summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/android/devil/android/decorators.py26
-rw-r--r--build/android/devil/android/decorators_test.py30
-rw-r--r--build/android/devil/android/sdk/adb_wrapper.py8
-rw-r--r--build/android/devil/utils/timeout_retry.py16
4 files changed, 71 insertions, 9 deletions
diff --git a/build/android/devil/android/decorators.py b/build/android/devil/android/decorators.py
index b263790..004ac8bc 100644
--- a/build/android/devil/android/decorators.py
+++ b/build/android/devil/android/decorators.py
@@ -19,7 +19,9 @@ DEFAULT_TIMEOUT_ATTR = '_default_timeout'
DEFAULT_RETRIES_ATTR = '_default_retries'
-def _TimeoutRetryWrapper(f, timeout_func, retries_func, pass_values=False):
+def _TimeoutRetryWrapper(
+ f, timeout_func, retries_func, retry_if_func=timeout_retry.AlwaysRetry,
+ pass_values=False):
""" Wraps a funcion with timeout and retry handling logic.
Args:
@@ -50,7 +52,8 @@ def _TimeoutRetryWrapper(f, timeout_func, retries_func, pass_values=False):
desc = '%s(%s)' % (f.__name__, ', '.join(itertools.chain(
(str(a) for a in args),
('%s=%s' % (k, str(v)) for k, v in kwargs.iteritems()))))
- return timeout_retry.Run(impl, timeout, retries, desc=desc)
+ return timeout_retry.Run(impl, timeout, retries, desc=desc,
+ retry_if_func=retry_if_func)
except reraiser_thread.TimeoutError as e:
raise device_errors.CommandTimeoutError(str(e)), None, (
sys.exc_info()[2])
@@ -75,6 +78,25 @@ def WithTimeoutAndRetries(f):
return _TimeoutRetryWrapper(f, get_timeout, get_retries)
+def WithTimeoutAndConditionalRetries(retry_if_func):
+ """Returns a decorator that handles timeouts and, in some cases, retries.
+
+ 'timeout' and 'retries' kwargs must be passed to the function.
+
+ Args:
+ retry_if_func: A unary callable that takes an exception and returns
+ whether failures should be retried.
+ Returns:
+ The actual decorator.
+ """
+ def decorator(f):
+ get_timeout = lambda *a, **kw: kw['timeout']
+ get_retries = lambda *a, **kw: kw['retries']
+ return _TimeoutRetryWrapper(
+ f, get_timeout, get_retries, retry_if_func=retry_if_func)
+ return decorator
+
+
def WithExplicitTimeoutAndRetries(timeout, retries):
"""Returns a decorator that handles timeouts and retries.
diff --git a/build/android/devil/android/decorators_test.py b/build/android/devil/android/decorators_test.py
index 1b6179e6..a00b128 100644
--- a/build/android/devil/android/decorators_test.py
+++ b/build/android/devil/android/decorators_test.py
@@ -82,6 +82,36 @@ class DecoratorsTest(unittest.TestCase):
timeout=10, retries=1)
self.assertEquals(exception_desc, str(e.exception))
+ def testConditionalRetriesDecoratorRetries(self):
+ def do_not_retry_no_adb_error(exc):
+ return not isinstance(exc, device_errors.NoAdbError)
+
+ actual_tries = [0]
+
+ @decorators.WithTimeoutAndConditionalRetries(do_not_retry_no_adb_error)
+ def alwaysRaisesCommandFailedError(timeout=None, retries=None):
+ actual_tries[0] += 1
+ raise device_errors.CommandFailedError('Command failed :(')
+
+ with self.assertRaises(device_errors.CommandFailedError):
+ alwaysRaisesCommandFailedError(timeout=10, retries=10)
+ self.assertEquals(11, actual_tries[0])
+
+ def testConditionalRetriesDecoratorDoesntRetry(self):
+ def do_not_retry_no_adb_error(exc):
+ return not isinstance(exc, device_errors.NoAdbError)
+
+ actual_tries = [0]
+
+ @decorators.WithTimeoutAndConditionalRetries(do_not_retry_no_adb_error)
+ def alwaysRaisesNoAdbError(timeout=None, retries=None):
+ actual_tries[0] += 1
+ raise device_errors.NoAdbError()
+
+ with self.assertRaises(device_errors.NoAdbError):
+ alwaysRaisesNoAdbError(timeout=10, retries=10)
+ self.assertEquals(1, actual_tries[0])
+
def testDefaultsFunctionDecoratorDoesTimeouts(self):
"""Tests that the defaults decorator handles timeout logic."""
DecoratorsTest._decorated_function_called_count = 0
diff --git a/build/android/devil/android/sdk/adb_wrapper.py b/build/android/devil/android/sdk/adb_wrapper.py
index 85d46ea..38db0e1 100644
--- a/build/android/devil/android/sdk/adb_wrapper.py
+++ b/build/android/devil/android/sdk/adb_wrapper.py
@@ -64,6 +64,10 @@ def _FindAdb():
raise device_errors.NoAdbError()
+def _ShouldRetryAdbCmd(exc):
+ return not isinstance(exc, device_errors.NoAdbError)
+
+
DeviceStat = collections.namedtuple('DeviceStat',
['st_mode', 'st_size', 'st_time'])
@@ -99,9 +103,9 @@ class AdbWrapper(object):
cmd.extend(args)
return cmd
- # pylint: disable=unused-argument
+ #pylint: disable=unused-argument
@classmethod
- @decorators.WithTimeoutAndRetries
+ @decorators.WithTimeoutAndConditionalRetries(_ShouldRetryAdbCmd)
def _RunAdbCmd(cls, args, timeout=None, retries=None, device_serial=None,
check_error=True, cpu_affinity=None):
# pylint: disable=no-member
diff --git a/build/android/devil/utils/timeout_retry.py b/build/android/devil/utils/timeout_retry.py
index 1d748a9..e950e11 100644
--- a/build/android/devil/utils/timeout_retry.py
+++ b/build/android/devil/utils/timeout_retry.py
@@ -125,8 +125,12 @@ def _LogLastException(thread_name, attempt, max_attempts, log_func):
log_func('*' * 80)
+def AlwaysRetry(_exception):
+ return True
+
+
def Run(func, timeout, retries, args=None, kwargs=None, desc=None,
- error_log_func=logging.critical):
+ error_log_func=logging.critical, retry_if_func=AlwaysRetry):
"""Runs the passed function in a separate thread with timeouts and retries.
Args:
@@ -138,6 +142,8 @@ def Run(func, timeout, retries, args=None, kwargs=None, desc=None,
desc: An optional description of |func| used in logging. If omitted,
|func.__name__| will be used.
error_log_func: Logging function when logging errors.
+ retry_if_func: Unary callable that takes an exception and returns
+ whether |func| should be retried. Defaults to always retrying.
Returns:
The return value of func(*args, **kwargs).
@@ -163,13 +169,13 @@ def Run(func, timeout, retries, args=None, kwargs=None, desc=None,
logging.info('Still working on %s', desc if desc else func.__name__)
else:
return thread_group.GetAllReturnValues()[0]
- except reraiser_thread.TimeoutError:
+ except reraiser_thread.TimeoutError as e:
# Timeouts already get their stacks logged.
- if num_try > retries:
+ if num_try > retries or not retry_if_func(e):
raise
# Do not catch KeyboardInterrupt.
- except Exception: # pylint: disable=broad-except
- if num_try > retries:
+ except Exception as e: # pylint: disable=broad-except
+ if num_try > retries or not retry_if_func(e):
raise
_LogLastException(thread_name, num_try, retries + 1, error_log_func)
num_try += 1