• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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