#!/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.
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