Add retry in downloading Android artifacts.
BUG=chromium:512668
TEST=local test
Change-Id: Ic084079fd9eac71ac4cca40a702f1337d9eca2aa
Reviewed-on: https://chromium-review.googlesource.com/312503
Commit-Ready: Dan Shi <dshi@google.com>
Tested-by: Dan Shi <dshi@google.com>
Reviewed-by: Dan Shi <dshi@google.com>
diff --git a/retry.py b/retry.py
new file mode 100644
index 0000000..a0b51fb
--- /dev/null
+++ b/retry.py
@@ -0,0 +1,79 @@
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Basic infrastructure for implementing retries.
+
+This code is adopted from autotest: client/common_lib/cros/retry.py
+This implementation removes the timeout feature as that requires the retry to
+be done in main thread. For devserver, the call is handled in a thread kicked
+off by cherrypy, so timeotu can't be supported.
+"""
+
+from __future__ import print_function
+
+import cherrypy
+import random
+import sys
+import time
+
+
+def retry(ExceptionToCheck, timeout_min=1.0, delay_sec=3, blacklist=None):
+ """Retry calling the decorated function using a delay with jitter.
+
+ Will raise RPC ValidationError exceptions from the decorated
+ function without retrying; a malformed RPC isn't going to
+ magically become good. Will raise exceptions in blacklist as well.
+
+ original from:
+ http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
+
+ Args:
+ ExceptionToCheck: the exception to check. May be a tuple of exceptions to
+ check.
+ timeout_min: timeout in minutes until giving up.
+ delay_sec: pre-jittered delay between retries in seconds. Actual delays
+ will be centered around this value, ranging up to 50% off this
+ midpoint.
+ blacklist: a list of exceptions that will be raised without retrying
+ """
+ def deco_retry(func):
+ random.seed()
+
+ def delay():
+ """'Jitter' the delay, up to 50% in either direction."""
+ random_delay = random.uniform(.5 * delay_sec, 1.5 * delay_sec)
+ cherrypy.log('Retrying in %f seconds...' % random_delay)
+ time.sleep(random_delay)
+
+ def func_retry(*args, **kwargs):
+ # Used to cache exception to be raised later.
+ exc_info = None
+ delayed_enabled = False
+ exception_tuple = () if blacklist is None else tuple(blacklist)
+ start_time = time.time()
+ remaining_time = timeout_min * 60
+
+ while remaining_time > 0:
+ if delayed_enabled:
+ delay()
+ else:
+ delayed_enabled = True
+ try:
+ # Clear the cache
+ exc_info = None
+ return func(*args, **kwargs)
+ except exception_tuple:
+ raise
+ except ExceptionToCheck as e:
+ cherrypy.log('%s(%s)' % (e.__class__, e))
+ # Cache the exception to be raised later.
+ exc_info = sys.exc_info()
+
+ remaining_time = int(timeout_min*60 - (time.time() - start_time))
+
+ # Raise the cached exception with original backtrace.
+ raise exc_info[0], exc_info[1], exc_info[2]
+
+ return func_retry # true decorator
+ return deco_retry