Merge branch '8784-dir-listings'
[arvados.git] / sdk / python / arvados / retry.py
index 3d2fc48e31fafbe93e1cddfcd8703dbf982bd2cd..3f62ab779f81fa43537a8223b7348bed52dc3a7c 100644 (file)
@@ -1,14 +1,19 @@
-#!/usr/bin/env python
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
 
+from builtins import range
+from builtins import object
 import functools
 import inspect
+import pycurl
 import time
 
 from collections import deque
 
 import arvados.errors
 
-_HTTP_SUCCESSES = set(xrange(200, 300))
+_HTTP_SUCCESSES = set(range(200, 300))
 _HTTP_CAN_RETRY = set([408, 409, 422, 423, 500, 502, 503, 504])
 
 class RetryLoop(object):
@@ -30,7 +35,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:
@@ -49,11 +55,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
@@ -65,7 +73,7 @@ class RetryLoop(object):
     def running(self):
         return self._running and (self._success is None)
 
-    def next(self):
+    def __next__(self):
         if self._running is None:
             self._running = True
         if (self.tries_left < 1) or not self.running():
@@ -75,6 +83,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
@@ -109,12 +119,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:
@@ -128,15 +138,11 @@ 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.
@@ -151,8 +157,7 @@ def retry_method(orig_func):
     """
     @functools.wraps(orig_func)
     def num_retries_setter(self, *args, **kwargs):
-        arg_vals = inspect.getcallargs(orig_func, self, *args, **kwargs)
-        if arg_vals['num_retries'] is None:
+        if kwargs.get('num_retries') is None:
             kwargs['num_retries'] = self.num_retries
         return orig_func(self, *args, **kwargs)
     return num_retries_setter