Home | History | Annotate | Download | only in android
      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 """
      6 Function/method decorators that provide timeout and retry logic.
      7 """
      8 
      9 import functools
     10 import itertools
     11 import sys
     12 
     13 from devil.android import device_errors
     14 from devil.utils import cmd_helper
     15 from devil.utils import reraiser_thread
     16 from devil.utils import timeout_retry
     17 
     18 DEFAULT_TIMEOUT_ATTR = '_default_timeout'
     19 DEFAULT_RETRIES_ATTR = '_default_retries'
     20 
     21 
     22 def _TimeoutRetryWrapper(
     23     f, timeout_func, retries_func, retry_if_func=timeout_retry.AlwaysRetry,
     24     pass_values=False):
     25   """ Wraps a funcion with timeout and retry handling logic.
     26 
     27   Args:
     28     f: The function to wrap.
     29     timeout_func: A callable that returns the timeout value.
     30     retries_func: A callable that returns the retries value.
     31     pass_values: If True, passes the values returned by |timeout_func| and
     32                  |retries_func| to the wrapped function as 'timeout' and
     33                  'retries' kwargs, respectively.
     34   Returns:
     35     The wrapped function.
     36   """
     37   @functools.wraps(f)
     38   def timeout_retry_wrapper(*args, **kwargs):
     39     timeout = timeout_func(*args, **kwargs)
     40     retries = retries_func(*args, **kwargs)
     41     if pass_values:
     42       kwargs['timeout'] = timeout
     43       kwargs['retries'] = retries
     44 
     45     @functools.wraps(f)
     46     def impl():
     47       return f(*args, **kwargs)
     48     try:
     49       if timeout_retry.CurrentTimeoutThreadGroup():
     50         # Don't wrap if there's already an outer timeout thread.
     51         return impl()
     52       else:
     53         desc = '%s(%s)' % (f.__name__, ', '.join(itertools.chain(
     54             (str(a) for a in args),
     55             ('%s=%s' % (k, str(v)) for k, v in kwargs.iteritems()))))
     56         return timeout_retry.Run(impl, timeout, retries, desc=desc,
     57                                  retry_if_func=retry_if_func)
     58     except reraiser_thread.TimeoutError as e:
     59       raise device_errors.CommandTimeoutError(str(e)), None, (
     60           sys.exc_info()[2])
     61     except cmd_helper.TimeoutError as e:
     62       raise device_errors.CommandTimeoutError(str(e)), None, (
     63           sys.exc_info()[2])
     64   return timeout_retry_wrapper
     65 
     66 
     67 def WithTimeoutAndRetries(f):
     68   """A decorator that handles timeouts and retries.
     69 
     70   'timeout' and 'retries' kwargs must be passed to the function.
     71 
     72   Args:
     73     f: The function to decorate.
     74   Returns:
     75     The decorated function.
     76   """
     77   get_timeout = lambda *a, **kw: kw['timeout']
     78   get_retries = lambda *a, **kw: kw['retries']
     79   return _TimeoutRetryWrapper(f, get_timeout, get_retries)
     80 
     81 
     82 def WithTimeoutAndConditionalRetries(retry_if_func):
     83   """Returns a decorator that handles timeouts and, in some cases, retries.
     84 
     85   'timeout' and 'retries' kwargs must be passed to the function.
     86 
     87   Args:
     88     retry_if_func: A unary callable that takes an exception and returns
     89       whether failures should be retried.
     90   Returns:
     91     The actual decorator.
     92   """
     93   def decorator(f):
     94     get_timeout = lambda *a, **kw: kw['timeout']
     95     get_retries = lambda *a, **kw: kw['retries']
     96     return _TimeoutRetryWrapper(
     97         f, get_timeout, get_retries, retry_if_func=retry_if_func)
     98   return decorator
     99 
    100 
    101 def WithExplicitTimeoutAndRetries(timeout, retries):
    102   """Returns a decorator that handles timeouts and retries.
    103 
    104   The provided |timeout| and |retries| values are always used.
    105 
    106   Args:
    107     timeout: The number of seconds to wait for the decorated function to
    108              return. Always used.
    109     retries: The number of times the decorated function should be retried on
    110              failure. Always used.
    111   Returns:
    112     The actual decorator.
    113   """
    114   def decorator(f):
    115     get_timeout = lambda *a, **kw: timeout
    116     get_retries = lambda *a, **kw: retries
    117     return _TimeoutRetryWrapper(f, get_timeout, get_retries)
    118   return decorator
    119 
    120 
    121 def WithTimeoutAndRetriesDefaults(default_timeout, default_retries):
    122   """Returns a decorator that handles timeouts and retries.
    123 
    124   The provided |default_timeout| and |default_retries| values are used only
    125   if timeout and retries values are not provided.
    126 
    127   Args:
    128     default_timeout: The number of seconds to wait for the decorated function
    129                      to return. Only used if a 'timeout' kwarg is not passed
    130                      to the decorated function.
    131     default_retries: The number of times the decorated function should be
    132                      retried on failure. Only used if a 'retries' kwarg is not
    133                      passed to the decorated function.
    134   Returns:
    135     The actual decorator.
    136   """
    137   def decorator(f):
    138     get_timeout = lambda *a, **kw: kw.get('timeout', default_timeout)
    139     get_retries = lambda *a, **kw: kw.get('retries', default_retries)
    140     return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
    141   return decorator
    142 
    143 
    144 def WithTimeoutAndRetriesFromInstance(
    145     default_timeout_name=DEFAULT_TIMEOUT_ATTR,
    146     default_retries_name=DEFAULT_RETRIES_ATTR,
    147     min_default_timeout=None):
    148   """Returns a decorator that handles timeouts and retries.
    149 
    150   The provided |default_timeout_name| and |default_retries_name| are used to
    151   get the default timeout value and the default retries value from the object
    152   instance if timeout and retries values are not provided.
    153 
    154   Note that this should only be used to decorate methods, not functions.
    155 
    156   Args:
    157     default_timeout_name: The name of the default timeout attribute of the
    158                           instance.
    159     default_retries_name: The name of the default retries attribute of the
    160                           instance.
    161     min_timeout: Miniumum timeout to be used when using instance timeout.
    162   Returns:
    163     The actual decorator.
    164   """
    165   def decorator(f):
    166     def get_timeout(inst, *_args, **kwargs):
    167       ret = getattr(inst, default_timeout_name)
    168       if min_default_timeout is not None:
    169         ret = max(min_default_timeout, ret)
    170       return kwargs.get('timeout', ret)
    171 
    172     def get_retries(inst, *_args, **kwargs):
    173       return kwargs.get('retries', getattr(inst, default_retries_name))
    174     return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
    175   return decorator
    176 
    177