1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6Function/method decorators that provide timeout and retry logic. 7""" 8 9import functools 10import os 11import sys 12 13from pylib import constants 14from pylib.device import device_errors 15from pylib.utils import reraiser_thread 16from pylib.utils import timeout_retry 17 18# TODO(jbudorick) Remove once the DeviceUtils implementations are no longer 19# backed by AndroidCommands / android_testrunner. 20sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 21 'android_testrunner')) 22import errors as old_errors 23 24DEFAULT_TIMEOUT_ATTR = '_default_timeout' 25DEFAULT_RETRIES_ATTR = '_default_retries' 26 27 28def _TimeoutRetryWrapper(f, timeout_func, retries_func, pass_values=False): 29 """ Wraps a funcion with timeout and retry handling logic. 30 31 Args: 32 f: The function to wrap. 33 timeout_func: A callable that returns the timeout value. 34 retries_func: A callable that returns the retries value. 35 pass_values: If True, passes the values returned by |timeout_func| and 36 |retries_func| to the wrapped function as 'timeout' and 37 'retries' kwargs, respectively. 38 Returns: 39 The wrapped function. 40 """ 41 @functools.wraps(f) 42 def TimeoutRetryWrapper(*args, **kwargs): 43 timeout = timeout_func(*args, **kwargs) 44 retries = retries_func(*args, **kwargs) 45 if pass_values: 46 kwargs['timeout'] = timeout 47 kwargs['retries'] = retries 48 def impl(): 49 return f(*args, **kwargs) 50 try: 51 return timeout_retry.Run(impl, timeout, retries) 52 except old_errors.WaitForResponseTimedOutError as e: 53 raise device_errors.CommandTimeoutError(str(e)) 54 except old_errors.DeviceUnresponsiveError as e: 55 raise device_errors.DeviceUnreachableError(str(e)) 56 except reraiser_thread.TimeoutError as e: 57 raise device_errors.CommandTimeoutError(str(e)) 58 return TimeoutRetryWrapper 59 60 61def WithTimeoutAndRetries(f): 62 """A decorator that handles timeouts and retries. 63 64 'timeout' and 'retries' kwargs must be passed to the function. 65 66 Args: 67 f: The function to decorate. 68 Returns: 69 The decorated function. 70 """ 71 get_timeout = lambda *a, **kw: kw['timeout'] 72 get_retries = lambda *a, **kw: kw['retries'] 73 return _TimeoutRetryWrapper(f, get_timeout, get_retries) 74 75 76def WithExplicitTimeoutAndRetries(timeout, retries): 77 """Returns a decorator that handles timeouts and retries. 78 79 The provided |timeout| and |retries| values are always used. 80 81 Args: 82 timeout: The number of seconds to wait for the decorated function to 83 return. Always used. 84 retries: The number of times the decorated function should be retried on 85 failure. Always used. 86 Returns: 87 The actual decorator. 88 """ 89 def decorator(f): 90 get_timeout = lambda *a, **kw: timeout 91 get_retries = lambda *a, **kw: retries 92 return _TimeoutRetryWrapper(f, get_timeout, get_retries) 93 return decorator 94 95 96def WithTimeoutAndRetriesDefaults(default_timeout, default_retries): 97 """Returns a decorator that handles timeouts and retries. 98 99 The provided |default_timeout| and |default_retries| values are used only 100 if timeout and retries values are not provided. 101 102 Args: 103 default_timeout: The number of seconds to wait for the decorated function 104 to return. Only used if a 'timeout' kwarg is not passed 105 to the decorated function. 106 default_retries: The number of times the decorated function should be 107 retried on failure. Only used if a 'retries' kwarg is not 108 passed to the decorated function. 109 Returns: 110 The actual decorator. 111 """ 112 def decorator(f): 113 get_timeout = lambda *a, **kw: kw.get('timeout', default_timeout) 114 get_retries = lambda *a, **kw: kw.get('retries', default_retries) 115 return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True) 116 return decorator 117 118 119def WithTimeoutAndRetriesFromInstance( 120 default_timeout_name=DEFAULT_TIMEOUT_ATTR, 121 default_retries_name=DEFAULT_RETRIES_ATTR): 122 """Returns a decorator that handles timeouts and retries. 123 124 The provided |default_timeout_name| and |default_retries_name| are used to 125 get the default timeout value and the default retries value from the object 126 instance if timeout and retries values are not provided. 127 128 Note that this should only be used to decorate methods, not functions. 129 130 Args: 131 default_timeout_name: The name of the default timeout attribute of the 132 instance. 133 default_retries_name: The name of the default retries attribute of the 134 instance. 135 Returns: 136 The actual decorator. 137 """ 138 def decorator(f): 139 def get_timeout(inst, *_args, **kwargs): 140 return kwargs.get('timeout', getattr(inst, default_timeout_name)) 141 def get_retries(inst, *_args, **kwargs): 142 return kwargs.get('retries', getattr(inst, default_retries_name)) 143 return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True) 144 return decorator 145 146