X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/adbeb30cc27fa56a4550e28e1fd5e8ec4a1c7836..0f8dc3d824f03b82b8db9f61bbcc0592b62b998f:/sdk/python/arvados/retry.py diff --git a/sdk/python/arvados/retry.py b/sdk/python/arvados/retry.py index a5a303fa83..d8f5317d2c 100644 --- a/sdk/python/arvados/retry.py +++ b/sdk/python/arvados/retry.py @@ -1,11 +1,17 @@ #!/usr/bin/env python +import functools +import inspect +import pycurl import time from collections import deque import arvados.errors +_HTTP_SUCCESSES = set(xrange(200, 300)) +_HTTP_CAN_RETRY = set([408, 409, 422, 423, 500, 502, 503, 504]) + class RetryLoop(object): """Coordinate limited retries of code. @@ -102,3 +108,47 @@ class RetryLoop(object): except IndexError: raise arvados.errors.AssertionError( "queried loop results before any were recorded") + + +def check_http_response_success(status_code): + """Convert an HTTP status code to a loop control flag. + + Pass this method a numeric HTTP status code. It returns True if + the code indicates success, None if it indicates temporary + failure, and False otherwise. You can use this as the + success_check for a RetryLoop. + + Implementation details: + * Any 2xx result returns True. + * A select few status codes, or any malformed responses, return None. + 422 Unprocessable Entity is in this category. This may not meet the + letter of the HTTP specification, but the Arvados API server will + use it for various server-side problems like database connection + errors. + * Everything else returns False. Note that this includes 1xx and + 3xx status codes. They don't indicate success, and you can't + retry those requests verbatim. + """ + if status_code in _HTTP_SUCCESSES: + return True + elif status_code in _HTTP_CAN_RETRY: + return None + elif 100 <= status_code < 600: + return False + else: + return None # Get well soon, server. + +def retry_method(orig_func): + """Provide a default value for a method's num_retries argument. + + This is a decorator for instance and class methods that accept a + num_retries argument, with a None default. When the method is called + without a value for num_retries, it will be set from the underlying + instance or class' num_retries attribute. + """ + @functools.wraps(orig_func) + def num_retries_setter(self, *args, **kwargs): + if kwargs.get('num_retries') is None: + kwargs['num_retries'] = self.num_retries + return orig_func(self, *args, **kwargs) + return num_retries_setter