X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5a80356ddc3798f6530e306901d7ea5e19cfe3f9..8c5f2973a5c5f042d1d12aef1c470b37519fd416:/sdk/python/arvados/retry.py diff --git a/sdk/python/arvados/retry.py b/sdk/python/arvados/retry.py index 5dc31aefa7..5ba4f4ea41 100644 --- a/sdk/python/arvados/retry.py +++ b/sdk/python/arvados/retry.py @@ -1,5 +1,8 @@ #!/usr/bin/env python +import functools +import inspect +import pycurl import time from collections import deque @@ -28,7 +31,8 @@ class RetryLoop(object): return loop.last_result() """ def __init__(self, num_retries, success_check=lambda r: True, - backoff_start=0, backoff_growth=2, save_results=1): + backoff_start=0, backoff_growth=2, save_results=1, + max_wait=60): """Construct a new RetryLoop. Arguments: @@ -47,11 +51,13 @@ class RetryLoop(object): * save_results: Specify a number to save the last N results that the loop recorded. These records are available through the results attribute, oldest first. Default 1. + * max_wait: Maximum number of seconds to wait between retries. """ self.tries_left = num_retries + 1 self.check_result = success_check self.backoff_wait = backoff_start self.backoff_growth = backoff_growth + self.max_wait = max_wait self.next_start_time = 0 self.results = deque(maxlen=save_results) self._running = None @@ -73,6 +79,8 @@ class RetryLoop(object): wait_time = max(0, self.next_start_time - time.time()) time.sleep(wait_time) self.backoff_wait *= self.backoff_growth + if self.backoff_wait > self.max_wait: + self.backoff_wait = self.max_wait self.next_start_time = time.time() + self.backoff_wait self.tries_left -= 1 return self.tries_left @@ -107,12 +115,12 @@ class RetryLoop(object): "queried loop results before any were recorded") -def check_http_response_success(result): - """Convert an httplib2 request result to a loop control flag. +def check_http_response_success(status_code): + """Convert an HTTP status code to a loop control flag. - Pass this method the 2-tuple returned by httplib2.Http.request. It - returns True if the response indicates success, None if it indicates - temporary failure, and False otherwise. You can use this as the + 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: @@ -126,15 +134,26 @@ def check_http_response_success(result): 3xx status codes. They don't indicate success, and you can't retry those requests verbatim. """ - try: - status = int(result[0].status) - except Exception: - return None - if status in _HTTP_SUCCESSES: + if status_code in _HTTP_SUCCESSES: return True - elif status in _HTTP_CAN_RETRY: + elif status_code in _HTTP_CAN_RETRY: return None - elif 100 <= status < 600: + 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