From hb at biostat.ucsf.edu  Fri Jan 13 04:17:18 2012
From: hb at biostat.ucsf.edu (Henrik Bengtsson)
Date: Thu, 12 Jan 2012 19:17:18 -0800
Subject: [Rd] WISHLIST: Be able to timeout readline()/stdin via setTimeLimit
	in all consoles
Message-ID: <CAFDcVCQsT-WwaqJkZwvtBJ=g42w73LHSxt2sDzDheXM4B55XVg@mail.gmail.com>

Hi.

WISHLIST:
Regardless on console, I'd like to be able to timeout a call to
readline()/file("stdin", blocking=TRUE) via setTimeLimit.


OBSERVATION:
On Windows Rterm as well as plain R on Linux, setTimeLimit() does not
momentarily interrupt from stdin, but only after hitting RETURN.  A
few examples:

timeout00 <- function() {
  setTimeLimit(elapsed=5);
  Sys.sleep(10);
}

timeout01 <- function() {
  setTimeLimit(elapsed=5);
  readline();
}

timeout02 <- function() {
  setTimeLimit(elapsed=5);
  con <- file("stdin", blocking=TRUE);
  on.exit(close(con));
  readChar(con, nchars=1);
}

timeout03 <- function() {
  setTimeLimit(elapsed=5);
  con <- socketConnection(port=6011, blocking=TRUE);
  on.exit(close(con));
  readChar(con, nchars=1);
}

# Times out after 5 second
> timeout00()
Error in Sys.sleep(10) : reached elapsed time limit

# Times out only after pressing ENTER
> timeout01()
foo
[1] "foo"
Error: reached elapsed time limit

# Times out only after pressing ENTER
> timeout02()
bar
[1] "b"
Error: reached elapsed time limit

# Times out after 5 second
> timeout03()
Error in socketConnection(port = 6011, blocking = TRUE)
  reached elapsed time limit

Note also, that it is possible to interrupted timeout01() and
timeout02() via Ctrl+C as well as by sending SIGINT to the R process
(at least on a Linux system).


Doing the same in Rgui, the timeout will indeed timeout:

# Times out after 5 second
> timeout01()
Error in readline() : reached elapsed time limit

# Times out after 5 second (but somehow returns immediately)
> timeout02()
character(0)
Error: reached elapsed time limit



DOCUMENTATION:
The help("setTimeLimit", package="base") documentation says:

"Time limits are checked whenever a user interrupt could occur. This
will happen frequently in R code and during Sys.sleep, but only at
points in compiled C and Fortran code identified by the code author."

Maybe one could clarify/examplify(?) by adding: "Depending on the type
of console, timeouts when reading from stdio (e.g. via readline()) may
not be effective until a line is completed (i.e. ENTER is pressed).



HOW UPDATING SOURCE CODE TO SUPPORT INTERRUPTS?
I'm not sure where this "behavior" is originating from, and whether it
is possible to obtain a cross-platform solution or not.  I located the
following native do_readln() function in src/main/scan.c that is
called by readline():

SEXP attribute_hidden do_readln(SEXP call, SEXP op, SEXP args, SEXP rho)
{
...
	    while ((c = ConsoleGetchar())!= '\n' && c != R_EOF) {
		if (bufp >= &buffer[MAXELTSIZE - 2]) continue;
		*bufp++ = c;
	    }
...
}

with ConsoleGetchar():

/* used by readline() and menu() */
static int ConsoleGetchar(void)
{
    if (--ConsoleBufCnt < 0) {
	ConsoleBuf[CONSOLE_BUFFER_SIZE] = '\0';
	if (R_ReadConsole(ConsolePrompt, ConsoleBuf,
			  CONSOLE_BUFFER_SIZE, 0) == 0) {
	    R_ClearerrConsole();
	    return R_EOF;
	}
	ConsoleBufp = ConsoleBuf;
	ConsoleBufCnt = strlen((char *)ConsoleBuf);
	ConsoleBufCnt--;
    }
    /* at this point we need to use unsigned char or similar */
    return (int) *ConsoleBufp++;
}

where in turn R_ReadConsole() appears to be specific to platform and
console, e.g. from src/gnuwin32/system.c:

 * R_ReadConsole calls TrueReadConsole which points to:
 * case 1: GuiReadConsole
 * case 2 and 3: ThreadedReadConsole
 * case 4: FileReadConsole
 * ThreadedReadConsole wake up our 'reader thread' and wait until
 * a new line of input is available. The 'reader thread' uses
 * InThreadReadConsole to get it. InThreadReadConsole points to:
 * case 2: CharReadConsole
 * case 3: FileReadConsole

Here I get a bit lost, but there is:

/*2: from character console with getline (only used as InThreadReadConsole)*/
static int
CharReadConsole(const char *prompt, char *buf, int len, int addtohistory)
{
    int res = getline(prompt, buf, len);
    if (addtohistory) gl_histadd(buf);
    return !res;
}

and, AFAIU, getline() is from the GNU getline library.  Is it possible
to timeout getline()?  At least it appears to be responding to SIGINT.

/Henrik



[Rd] WISHLIST: Be able to timeout readline()/stdin via setTimeLimit in all consoles
Henrik Bengtsson