-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvements to RETRY (#404) #459
Changes from 11 commits
9c1a894
ab13969
1a488ab
f0ab424
8caafc7
b2527ef
d3967ca
0c88c26
cdc25c4
3f39739
64d0742
e9bff61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,11 @@ | ||
#' Retry a request until it succeeds. | ||
#' | ||
#' Safely retry a request until it succeeds (returns an HTTP status code | ||
#' below 400). It is designed to be kind to the server: after each failure | ||
#' Safely retry a request until it succeeds, as defined by the \code{terminate_on} | ||
#' parameter, which by default means a response for which \code{\link{http_error}()} | ||
#' is \code{FALSE}. Will also retry on error conditions raised by the underlying curl code, | ||
#' but if the last retry still raises one, \code{RETRY} will raise it again with | ||
#' \code{\link{stop}()} to maintain backwards compatibility. | ||
#' It is designed to be kind to the server: after each failure | ||
#' randomly waits up to twice as long. (Technically it uses exponential | ||
#' backoff with jitter, using the approach outlined in | ||
#' \url{https://www.awsarchitectureblog.com/2015/03/backoff.html}.) | ||
|
@@ -16,6 +20,9 @@ | |
#' \code{pause_cap} seconds. | ||
#' @param quiet If \code{FALSE}, will print a message displaying how long | ||
#' until the next request. | ||
#' @param terminate_on Optional vector of numeric HTTP status codes that if found | ||
#' on the response will terminate the retry process. If \code{NULL}, will keep | ||
#' retrying while \code{\link{http_error}()} is \code{TRUE} for the response. | ||
#' @return The last response. Note that if the request doesn't succeed after | ||
#' \code{times} times this will be a failed request, i.e. you still need | ||
#' to use \code{\link{stop_for_status}()}. | ||
|
@@ -25,33 +32,62 @@ | |
#' RETRY("GET", "http://httpbin.org/status/200") | ||
#' # Never succeeds | ||
#' RETRY("GET", "http://httpbin.org/status/500") | ||
#' \donttest{ | ||
#' # Invalid hostname generates curl error condition and is retried but eventually | ||
#' # raises an error condition. | ||
#' RETRY("GET", "http://invalidhostname/") | ||
#' } | ||
RETRY <- function(verb, url = NULL, config = list(), ..., | ||
body = NULL, encode = c("multipart", "form", "json", "raw"), | ||
times = 3, pause_base = 1, pause_cap = 60, | ||
handle = NULL, quiet = FALSE) { | ||
handle = NULL, quiet = FALSE, terminate_on = NULL) { | ||
stopifnot(is.numeric(times), length(times) == 1L) | ||
stopifnot(is.numeric(pause_base), length(pause_base) == 1L) | ||
stopifnot(is.numeric(pause_cap), length(pause_cap) == 1L) | ||
stopifnot(is.numeric(terminate_on) || is.null(terminate_on)) | ||
|
||
hu <- handle_url(handle, url, ...) | ||
req <- request_build(verb, hu$url, body_config(body, match.arg(encode)), config, ...) | ||
resp <- request_perform(req, hu$handle$handle) | ||
resp <- tryCatch(request_perform(req, hu$handle$handle), error = function(e) e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't love having a variable called |
||
|
||
i <- 1 | ||
while (i < times && http_error(resp)) { | ||
backoff_full_jitter(i, status_code(resp), pause_base, pause_cap, quiet = quiet) | ||
while (!retry_should_terminate(i, times, resp, terminate_on)) { | ||
backoff_full_jitter(i, resp, pause_base, pause_cap, quiet = quiet) | ||
|
||
i <- i + 1 | ||
resp <- request_perform(req, hu$handle$handle) | ||
resp <- tryCatch(request_perform(req, hu$handle$handle), error = function(e) e) | ||
} | ||
|
||
if (inherits(resp, "error")) { | ||
stop(resp) | ||
} | ||
|
||
resp | ||
} | ||
|
||
backoff_full_jitter <- function(i, status, pause_base = 1, pause_cap = 60, quiet = FALSE) { | ||
retry_should_terminate <- function(i, times, resp, terminate_on) { | ||
if (i >= times) { | ||
TRUE | ||
} else if (inherits(resp, "error")) { | ||
FALSE | ||
} else if (!is.null(terminate_on)) { | ||
status_code(resp) %in% terminate_on | ||
} else { | ||
!http_error(resp) | ||
} | ||
} | ||
|
||
backoff_full_jitter <- function(i, resp, pause_base = 1, pause_cap = 60, quiet = FALSE) { | ||
length <- ceiling(stats::runif(1, max = min(pause_cap, pause_base * (2 ^ i)))) | ||
if (!quiet) { | ||
message("Request failed [", status, "]. Retrying in ", length, " seconds...") | ||
if (inherits(resp, "error")) { | ||
error_description <- gsub("[\n\r]*$", "\n", as.character(resp)) | ||
status <- "ERROR" | ||
} else { | ||
error_description <- "" | ||
status <- status_code(resp) | ||
} | ||
message(error_description, "Request failed [", status, "]. Retrying in ", length, " seconds...") | ||
} | ||
Sys.sleep(length) | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please remove "to maintain backwards compatibility"