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