withTimeout.Rd
Evaluate an R expression and interrupts it if it takes too long.
withTimeout(expr, substitute=TRUE, envir=parent.frame(), timeout, cpu=timeout,
elapsed=timeout, onTimeout=c("error", "warning", "silent"), ...)
The R expression to be evaluated.
If TRUE
, argument expr
is
substitute()
:ed, otherwise not.
The environment
in which the expression should
be evaluated.
A numeric
specifying the maximum number
of seconds the expression is allowed to run before being
interrupted by the timeout. The cpu
and elapsed
arguments can be used to specify whether time should be measured
in CPU time or in wall time.
A character
specifying what action to take if
a timeout event occurs.
Not used.
Returns the results of the expression evaluated.
If timed out, NULL
is returned if onTimeout
was
"warning"
or "silent"
.
If "error"
a TimeoutException
is thrown.
This method utilizes setTimeLimit
() by first setting the
timeout limits, then evaluating the expression that may or may not
timeout. The method is guaranteed to reset the timeout limits to be
infinitely long upon exiting, regardless whether it returns normally
or preemptively due to a timeout or an error.
In order to understand when this function works and when it does not,
it is useful to know that it utilizes R's built-in time-out mechanism,
which sets the limits on what is possible and not.
From setTimeLimit
(), we learn that:
"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."
More precisely, if a function is implemented in native code (e.g. C) and the developer of that function does not check for user interrupts, then you cannot interrupt that function neither via a user interrupt (e.g. Ctrl-C) nor via the built-in time out mechanism. To change this, you need to contact the developer of that piece of code and ask them to check for R user interrupts in their native code.
Furthermore, it is not possible to interrupt/break out of a "readline"
prompt (e.g. readline
() and readLines
()) using
timeouts; the timeout exception will not be thrown until after the user
completes the prompt (i.e. after pressing ENTER).
System calls via system
() and system2()
cannot be
timed out via the above mechanisms. However, in R (>= 3.5.0) these
functions have argument timeout
providing their own independent
timeout mechanism.
Other examples of calls that do not support timeout are "atomic"
calls that may take very long such as large object allocation and
rnorm(n)
where n
is very large.
(*) Note that on Unix and macOS, Sys.sleep(time)
will signal a
timeout error only after time
seconds passed,
regardless of timeout
limit (< time
).
withTimeout()
does not handle the case when the expression
evaluated temporarily switches the language used by R, e.g.
assume we run in a non-French locale and call:
withTimeout({
olang <- Sys.getenv("LANGUAGE")
on.exit(Sys.setenv(LANGUAGE=olang))
Sys.setenv(LANGUAGE="fr")
repeat Sys.sleep(0.1)
}, timeout = 1.0, onTimeout = "warning")
In this case, the error message produced by setTimeLimit
() is
in French, i.e. `la limite de temps est atteinte`. However, when
withTimeout()
inspects this message, it can not know that
French was used, and will therefore not check against the French template
message for timeout errors. Because of this, withTimeout()
fails
to detect the timeout error (and therefore also deescalate it to a
warning in this example).
Comment: This appears to only fail on MS Windows and macOS,
whereas on Linux, withTimeout()
appears to work, but it is
unknown why there is a difference between operating systems in this
case.
Internally, eval
() is used to evaluate the expression and
setTimeLimit
() is used to control for timeout events.
[1] R help thread 'Time out for a R Function' on 2010-12-07.
https://stat.ethz.ch/pipermail/r-help/2010-December/262316.html
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Function that takes "a long" time to run
# - - - - - - - - - - - - - - - - - - - - - - - - -
foo <- function() {
print("Tic")
for (kk in 1:100) {
print(kk)
Sys.sleep(0.1)
}
print("Tac")
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Evaluate code, if it takes too long, generate
# a timeout by throwing a TimeoutException.
# - - - - - - - - - - - - - - - - - - - - - - - - -
res <- NULL
tryCatch({
res <- withTimeout({
foo()
}, timeout = 0.75)
}, TimeoutException = function(ex) {
message("Timeout. Skipping.")
})
#> [1] "Tic"
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10
#> [1] 11
#> Timeout. Skipping.
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Evaluate code, if it takes too long, generate
# a timeout returning NULL and generate a warning.
# - - - - - - - - - - - - - - - - - - - - - - - - -
res <- withTimeout({
foo()
}, timeout = 0.75, onTimeout = "warning")
#> [1] "Tic"
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> Warning: reached elapsed time limit [cpu=0.75s, elapsed=0.75s]
# The same using an expression object
expr <- quote(foo())
res <- withTimeout(expr, substitute = FALSE,
timeout = 0.75, onTimeout = "warning")
#> [1] "Tic"
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10
#> [1] 11
#> Warning: reached elapsed time limit [cpu=0.75s, elapsed=0.75s]
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Evaluate code, if it takes too long, generate
# a timeout, and return silently NULL.
# - - - - - - - - - - - - - - - - - - - - - - - - -
res <- withTimeout({
foo()
}, timeout = 0.75, onTimeout = "silent")
#> [1] "Tic"
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10
#> [1] 11