Reputation: 3562
I'm trying to do a thing "the right way". Sometimes "the right way" takes too long, depending on the inputs. I can't really know a priori when this will be. When "the right way" is taking too long, I want to go to "the hackish way". How do I make R monitor how long a particular task as taken, and give it something else to do if a threshold has passed? I'd imagine that this will be part of the try
family, but I'm not quite sure what to call it or google for.
Dummy example below. When slow.func
takes too long, I want interuptor
to stop it and call fast.func
instead.
slow.func <- function(x){
Sys.sleep(x)
print('good morning')
}
fast.func <- function(x){
Sys.sleep(x/10)
print('hit snooze')
}
interuptor = function(FUN,args, time.limit, ALTFUN){
# START MONITORING TIME HERE
do.call(FUN,args)
# IF FUN TAKES TOO LONG, STOP IT, CALL A
do.call(ALTFUN,args)
}
interuptor(slow.func, list(x = 2), time.limit = 1, fast.func)
Upvotes: 26
Views: 8795
Reputation: 71
The initial version I posted worked with "R.utils v2.5.0 (2016-11-07)" but it does not with "R.utils v2.9.2". Below a version with some modifications that works using "R.utils v2.9.2"
The answer of "nwknoblauch" does not work for me unless I change "warning" by "silent" inside the interruptor function.
library(R.utils)
slow.func <- function(x){
Sys.sleep(10)
return(x^2)
}
fast.func <- function(x){
Sys.sleep(2)
return(x*x)
}
interruptor = function(FUN,args, time.limit, ALTFUN){
results <- NULL
results <- evalWithTimeout({FUN(args)},timeout=time.limit,onTimeout="silent")
if(is.null(results)){
results <- ALTFUN(args)
}
return(results)
}
interruptor(FUN = slow.func,args=2,time.limit=3,ALTFUN = fast.func)
library(R.utils)
slow.func <- function(x){
Sys.sleep(4)
return(x^2)
}
fast.func <- function(x){
Sys.sleep(2)
return(x)
}
interruptor <- function(FUN,args, time.limit, ALTFUN){
results <-
tryCatch({
withTimeout({FUN(args)}, timeout=time.limit)
}, error = function(e){
if(grepl("reached elapsed time limit",e$message))
ALTFUN(args) else
paste(e$message,"EXTRACTERROR")
})
if(grepl("EXTRACTERROR",results)){
print(gsub("EXTRACTERROR","",results))
results <- NULL
}
return(results)
}
Depending on the selected time.limit, it executes the first function or the alternative. It returns NULL when there is an error not related to time limit and print the error message.
EXAMPLE:
test_obj <- interruptor(FUN = slow.func, args=5, time.limit= 6, ALTFUN = fast.func)
test_obj
test_obj <- interruptor(FUN = slow.func, args=5, time.limit= 3, ALTFUN = fast.func)
test_obj
test_obj <- interruptor(FUN = slow.func, args="A", time.limit= 6, ALTFUN = fast.func)
test_obj
test_obj <- interruptor(FUN = slow.func, args="A", time.limit= 3, ALTFUN = fast.func)
test_obj
Thanks to andybega for the idea of how improving the issue of error messages
Upvotes: 5
Reputation: 1437
For anyone who wants a lighter weight solution that does not depend on the R.utils
package, I ended up using a minimal solution based on the withTimeout()
code.
foo <- function() {
time_limit <- 10
setTimeLimit(cpu = time_limit, elapsed = time_limit, transient = TRUE)
on.exit({
setTimeLimit(cpu = Inf, elapsed = Inf, transient = FALSE)
})
tryCatch({
# do some stuff
}, error = function(e) {
if (grepl("reached elapsed time limit|reached CPU time limit", e$message)) {
# we reached timeout, apply some alternative method or do something else
} else {
# error not related to timeout
stop(e)
}
})
}
Upvotes: 8
Reputation: 568
The R package R.utils
has a function evalWithTimeout
that's pretty much exactly what you're describing. If you don't want to install a package, evalWithTimeout
relies on the less user friendly R base function setTimeLimit
Your code would look something like this:
library(R.utils)
slow.func <- function(x){
Sys.sleep(10)
return(x^2)
}
fast.func <- function(x){
Sys.sleep(2)
return(x*x)
}
interruptor = function(FUN,args, time.limit, ALTFUN){
results <- NULL
results <- evalWithTimeout({FUN(args)},timeout=time.limit,onTimeout="warning")
if(results==NULL){
results <- ALTFUN(args)
}
return(results)
}
interruptor(slow.func,args=2,time.limit=3,fast.func)
Upvotes: 15