How to retry a statement on error?

17,349

Solution 1

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()
  )
} 

Solution 2

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.

Solution 3

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

Solution 4

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")
})

Solution 5

I like setting my object as an error from the start, also sometimes useful to add some sleep time if you're having connection problems:

res <- simpleError("Fake error to start")
counter <- 1
max_tries <- 10 
# Sys.sleep(2*counter)
while(inherits(res, "error") & counter < max_tries) { 
  res <- tryCatch({ your_fun(...) }, 
      error = function(e) e)
  counter <- counter + 1
}
Share:
17,349
mchen
Author by

mchen

Updated on June 14, 2022

Comments

  • mchen
    mchen almost 2 years

    How can I simply tell R to retry a statement a few times if it errors? E.g. I was hoping to do something like:

    tryCatch(dbGetQuery(...),           # Query database
        error = function(e) {
            if (is.locking.error(e))    # If database is momentarily locked
                retry(times = 3)        # retry dbGetQuery(...) 3 more times
            else {
                # Handle other errors
            }
        }
    )
    
  • horaceT
    horaceT over 6 years
    Should set attempt=0 at start.
  • Selcuk Akbas
    Selcuk Akbas over 4 years
    this needs to be & not OR :: while(inherits(res, "error") & counter < max_tries) {
  • philiporlando
    philiporlando almost 2 years
    Is it possible to incorporate a timeout for each attempt? For example, I have a function that should not take longer than 10 seconds to run. If it does take longer, I want this attempt to timeout, and then I want retry to try again. It looks like the timeout parameter within retry is limited to the global runtime, not each attempt.