blob: a0b51fb25c372c443b425ecaf100021bf0380776 [file] [log] [blame]
Dan Shi7b9b6a92015-11-12 01:00:29 -08001# 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
7This code is adopted from autotest: client/common_lib/cros/retry.py
8This implementation removes the timeout feature as that requires the retry to
9be done in main thread. For devserver, the call is handled in a thread kicked
10off by cherrypy, so timeotu can't be supported.
11"""
12
13from __future__ import print_function
14
15import cherrypy
16import random
17import sys
18import time
19
20
21def 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