How to retry a statement on error?
Here's a function to generate a custom condition to respond to
locked <- function(message="occurred", ...) {
cond <- simpleCondition(message, ...)
class(cond) <- c("locked", class(cond))
cond
}
and a function implemented to allow (an infinite number of) restarts
f <- function() {
cnt <- 0L
repeat {
again <- FALSE
cnt <- cnt + 1L
withRestarts({
## do work here, and if needed...
signalCondition(locked())
}, retry=function() {
again <<- TRUE
})
if (!again) break
}
cnt
}
and the use of withCallingHandlers
(to keep the context where the condition was signaled active unlike tryCatch
) to handle the locked
condition
withCallingHandlers({
n_tries <- 0L
f()
}, locked=function(e) {
n_tries <<- n_tries + 1L
if (n_retries < 3)
invokeRestart("retry")
})
I usually put the try
block in a loop,
and exit the loop when it no longer fails or the maximum number of attempts is reached.
some_function_that_may_fail <- function() {
if( runif(1) < .5 ) stop()
return(1)
}
r <- NULL
attempt <- 1
while( is.null(r) && attempt <= 3 ) {
attempt <- attempt + 1
try(
r <- some_function_that_may_fail()
)
}
I wrote a quick function that allows you to easily retry an operating a configurable number of times, with a configurable wait between attempts:
library(futile.logger)
library(utils)
retry <- function(expr, isError=function(x) "try-error" %in% class(x), maxErrors=5, sleep=0) {
attempts = 0
retval = try(eval(expr))
while (isError(retval)) {
attempts = attempts + 1
if (attempts >= maxErrors) {
msg = sprintf("retry: too many retries [[%s]]", capture.output(str(retval)))
flog.fatal(msg)
stop(msg)
} else {
msg = sprintf("retry: error in attempt %i/%i [[%s]]", attempts, maxErrors,
capture.output(str(retval)))
flog.error(msg)
warning(msg)
}
if (sleep > 0) Sys.sleep(sleep)
retval = try(eval(expr))
}
return(retval)
}
So you can just write val = retry(func_that_might_fail(param1, param2), maxErrors=10, sleep=2)
to retry calling that function with those parameters, give up after 10 errors, and sleep 2 seconds between attempts.
Also, you can redefine the meaning of what an error looks like by passing a different function as parameter isError
, which by default will catch an error signaled with stop
. This is useful if the function being called does something else on error, such as returning FALSE
or NULL
.
This is the alternative I've found so far that results in clearer, more readable code.
Hope this helps.
A solution without pre-assigning values and using for
instead of while
:
some_function_that_may_fail <- function(i) {
if( runif(1) < .5 ) stop()
return(i)
}
for(i in 1:10){
try({
r <- some_function_that_may_fail(i)
break #break/exit the for-loop
}, silent = FALSE)
}
r
will be equal to the number of tries that were needed. If you dont want the output of the errors set silent
to TRUE