• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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.
4from __future__ import absolute_import
5from __future__ import division
6from __future__ import print_function
7import functools
8import logging
9import time
10from six.moves import range # pylint: disable=redefined-builtin
11
12
13def RetryOnException(exc_type, retries):
14  """Decorator to retry running a function if an exception is raised.
15
16  Implements exponential backoff to wait between each retry attempt, starting
17  with 1 second.
18
19  Note: the default number of retries is defined on the decorator, the decorated
20  function *must* also receive a "retries" argument (although its assigned
21  default value is ignored), and clients of the funtion may override the actual
22  number of retries at the call site.
23
24  The "unused" retries argument on the decorated function must be given to
25  keep pylint happy and to avoid breaking the Principle of Least Astonishment
26  if the decorator were to change the signature of the function.
27
28  For example:
29
30    @retry_util.RetryOnException(OSError, retries=3)  # default no. of retries
31    def ProcessSomething(thing, retries=None):  # this default value is ignored
32      del retries  # Unused. Handled by the decorator.
33      # Do your thing processing here, maybe sometimes raising exeptions.
34
35    ProcessSomething(a_thing)  # retries 3 times.
36    ProcessSomething(b_thing, retries=5)  # retries 5 times.
37
38  Args:
39    exc_type: An exception type (or a tuple of them), on which to retry.
40    retries: Default number of extra attempts to try, the caller may also
41      override this number. If an exception is raised during the last try,
42      then the exception is not caught and passed back to the caller.
43  """
44  def Decorator(f):
45    @functools.wraps(f)
46    def Wrapper(*args, **kwargs):
47      wait = 1
48      kwargs.setdefault('retries', retries)
49      for _ in range(kwargs['retries']):
50        try:
51          return f(*args, **kwargs)
52        except exc_type as exc:
53          logging.warning(
54              '%s raised %s, will retry in %d second%s ...',
55              f.__name__, type(exc).__name__, wait, '' if wait == 1 else 's')
56          time.sleep(wait)
57          wait *= 2
58      # Last try with no exception catching.
59      return f(*args, **kwargs)
60    return Wrapper
61  return Decorator
62