Dan Shi | 7b9b6a9 | 2015-11-12 01:00:29 -0800 | [diff] [blame] | 1 | # Copyright 2015 The Chromium OS 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 | """Basic infrastructure for implementing retries. |
| 6 | |
| 7 | This code is adopted from autotest: client/common_lib/cros/retry.py |
| 8 | This implementation removes the timeout feature as that requires the retry to |
| 9 | be done in main thread. For devserver, the call is handled in a thread kicked |
| 10 | off by cherrypy, so timeotu can't be supported. |
| 11 | """ |
| 12 | |
| 13 | from __future__ import print_function |
| 14 | |
| 15 | import cherrypy |
| 16 | import random |
| 17 | import sys |
| 18 | import time |
| 19 | |
| 20 | |
| 21 | def retry(ExceptionToCheck, timeout_min=1.0, delay_sec=3, blacklist=None): |
| 22 | """Retry calling the decorated function using a delay with jitter. |
| 23 | |
| 24 | Will raise RPC ValidationError exceptions from the decorated |
| 25 | function without retrying; a malformed RPC isn't going to |
| 26 | magically become good. Will raise exceptions in blacklist as well. |
| 27 | |
| 28 | original from: |
| 29 | http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/ |
| 30 | |
| 31 | Args: |
| 32 | ExceptionToCheck: the exception to check. May be a tuple of exceptions to |
| 33 | check. |
| 34 | timeout_min: timeout in minutes until giving up. |
| 35 | delay_sec: pre-jittered delay between retries in seconds. Actual delays |
| 36 | will be centered around this value, ranging up to 50% off this |
| 37 | midpoint. |
| 38 | blacklist: a list of exceptions that will be raised without retrying |
| 39 | """ |
| 40 | def deco_retry(func): |
| 41 | random.seed() |
| 42 | |
| 43 | def delay(): |
| 44 | """'Jitter' the delay, up to 50% in either direction.""" |
| 45 | random_delay = random.uniform(.5 * delay_sec, 1.5 * delay_sec) |
| 46 | cherrypy.log('Retrying in %f seconds...' % random_delay) |
| 47 | time.sleep(random_delay) |
| 48 | |
| 49 | def func_retry(*args, **kwargs): |
| 50 | # Used to cache exception to be raised later. |
| 51 | exc_info = None |
| 52 | delayed_enabled = False |
| 53 | exception_tuple = () if blacklist is None else tuple(blacklist) |
| 54 | start_time = time.time() |
| 55 | remaining_time = timeout_min * 60 |
| 56 | |
| 57 | while remaining_time > 0: |
| 58 | if delayed_enabled: |
| 59 | delay() |
| 60 | else: |
| 61 | delayed_enabled = True |
| 62 | try: |
| 63 | # Clear the cache |
| 64 | exc_info = None |
| 65 | return func(*args, **kwargs) |
| 66 | except exception_tuple: |
| 67 | raise |
| 68 | except ExceptionToCheck as e: |
| 69 | cherrypy.log('%s(%s)' % (e.__class__, e)) |
| 70 | # Cache the exception to be raised later. |
| 71 | exc_info = sys.exc_info() |
| 72 | |
| 73 | remaining_time = int(timeout_min*60 - (time.time() - start_time)) |
| 74 | |
| 75 | # Raise the cached exception with original backtrace. |
| 76 | raise exc_info[0], exc_info[1], exc_info[2] |
| 77 | |
| 78 | return func_retry # true decorator |
| 79 | return deco_retry |