# Copyright 2014 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """ Function/method decorators that provide timeout and retry logic. """ import functools import os import sys import threading from pylib import cmd_helper from pylib import constants from pylib.device import device_errors from pylib.utils import reraiser_thread from pylib.utils import timeout_retry # TODO(jbudorick) Remove once the DeviceUtils implementations are no longer # backed by AndroidCommands / android_testrunner. sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner')) import errors as old_errors DEFAULT_TIMEOUT_ATTR = '_default_timeout' DEFAULT_RETRIES_ATTR = '_default_retries' def _TimeoutRetryWrapper(f, timeout_func, retries_func, pass_values=False): """ Wraps a funcion with timeout and retry handling logic. Args: f: The function to wrap. timeout_func: A callable that returns the timeout value. retries_func: A callable that returns the retries value. pass_values: If True, passes the values returned by |timeout_func| and |retries_func| to the wrapped function as 'timeout' and 'retries' kwargs, respectively. Returns: The wrapped function. """ @functools.wraps(f) def TimeoutRetryWrapper(*args, **kwargs): timeout = timeout_func(*args, **kwargs) retries = retries_func(*args, **kwargs) if pass_values: kwargs['timeout'] = timeout kwargs['retries'] = retries def impl(): return f(*args, **kwargs) try: if isinstance(threading.current_thread(), timeout_retry.TimeoutRetryThread): return impl() else: return timeout_retry.Run(impl, timeout, retries) except old_errors.WaitForResponseTimedOutError as e: raise device_errors.CommandTimeoutError(str(e)), None, ( sys.exc_info()[2]) except old_errors.DeviceUnresponsiveError as e: raise device_errors.DeviceUnreachableError(str(e)), None, ( sys.exc_info()[2]) except reraiser_thread.TimeoutError as e: raise device_errors.CommandTimeoutError(str(e)), None, ( sys.exc_info()[2]) except cmd_helper.TimeoutError as e: raise device_errors.CommandTimeoutError(str(e)), None, ( sys.exc_info()[2]) return TimeoutRetryWrapper def WithTimeoutAndRetries(f): """A decorator that handles timeouts and retries. 'timeout' and 'retries' kwargs must be passed to the function. Args: f: The function to decorate. Returns: The decorated function. """ get_timeout = lambda *a, **kw: kw['timeout'] get_retries = lambda *a, **kw: kw['retries'] return _TimeoutRetryWrapper(f, get_timeout, get_retries) def WithExplicitTimeoutAndRetries(timeout, retries): """Returns a decorator that handles timeouts and retries. The provided |timeout| and |retries| values are always used. Args: timeout: The number of seconds to wait for the decorated function to return. Always used. retries: The number of times the decorated function should be retried on failure. Always used. Returns: The actual decorator. """ def decorator(f): get_timeout = lambda *a, **kw: timeout get_retries = lambda *a, **kw: retries return _TimeoutRetryWrapper(f, get_timeout, get_retries) return decorator def WithTimeoutAndRetriesDefaults(default_timeout, default_retries): """Returns a decorator that handles timeouts and retries. The provided |default_timeout| and |default_retries| values are used only if timeout and retries values are not provided. Args: default_timeout: The number of seconds to wait for the decorated function to return. Only used if a 'timeout' kwarg is not passed to the decorated function. default_retries: The number of times the decorated function should be retried on failure. Only used if a 'retries' kwarg is not passed to the decorated function. Returns: The actual decorator. """ def decorator(f): get_timeout = lambda *a, **kw: kw.get('timeout', default_timeout) get_retries = lambda *a, **kw: kw.get('retries', default_retries) return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True) return decorator def WithTimeoutAndRetriesFromInstance( default_timeout_name=DEFAULT_TIMEOUT_ATTR, default_retries_name=DEFAULT_RETRIES_ATTR): """Returns a decorator that handles timeouts and retries. The provided |default_timeout_name| and |default_retries_name| are used to get the default timeout value and the default retries value from the object instance if timeout and retries values are not provided. Note that this should only be used to decorate methods, not functions. Args: default_timeout_name: The name of the default timeout attribute of the instance. default_retries_name: The name of the default retries attribute of the instance. Returns: The actual decorator. """ def decorator(f): def get_timeout(inst, *_args, **kwargs): return kwargs.get('timeout', getattr(inst, default_timeout_name)) def get_retries(inst, *_args, **kwargs): return kwargs.get('retries', getattr(inst, default_retries_name)) return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True) return decorator