blob: bb92b10507fbc7ae53584594489454ceaf335501 [file] [log] [blame]
Chris Masone6f109082012-07-18 14:21:38 -07001# Copyright (c) 2012 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
Dan Shi6d31f802013-01-11 14:46:12 -08005import logging, random, signal, sys, time
6
Chris Masone6f109082012-07-18 14:21:38 -07007from autotest_lib.client.common_lib import error
8from autotest_lib.frontend.afe.json_rpc import proxy
9
10
Dan Shi6d31f802013-01-11 14:46:12 -080011class TimeoutException(Exception):
12 """
13 Exception to be raised for when alarm is triggered.
14 """
15 pass
16
17
18def handler(signum, frame):
19 """
20 Register a handler for the timeout.
21 """
22 raise TimeoutException('Call is timed out.')
23
24
25def timeout(func, args=(), kwargs={}, timeout_sec=60.0, default_result=None):
26 """
27 This function run the given function using the args, kwargs and
28 return the given default value if the timeout_sec is exceeded.
29
30 @param func: function to be called.
31 @param args: arguments for function to be called.
32 @param kwargs: keyword arguments for function to be called.
33 @param timeout_sec: timeout setting for call to exit, in seconds.
34 @param default_result: default return value for the function call.
35
36 @return 1: is_timeout 2: result of the function call. If
37 is_timeout is True, the call is timed out. If the
38 value is False, the call is finished on time.
39 """
40 old_handler = signal.signal(signal.SIGALRM, handler)
41
42 timeout_sec_n = int(timeout_sec)
43 # In case the timeout is rounded to 0, force to set it to default value.
44 if timeout_sec_n == 0:
45 timeout_sec_n = 60
Dan Shi6d31f802013-01-11 14:46:12 -080046 try:
Dan Shic0eae512013-01-11 14:46:12 -080047 old_alarm_sec = signal.alarm(timeout_sec_n)
48 if old_alarm_sec > 0:
49 old_timeout_time = time.time() + old_alarm_sec
Dan Shi6d31f802013-01-11 14:46:12 -080050 default_result = func(*args, **kwargs)
51 return False, default_result
52 except TimeoutException:
53 return True, default_result
54 finally:
55 # Cancel the timer if the function returned before timeout or
56 # exception being thrown.
57 signal.alarm(0)
58 # Restore previous Signal handler and alarm
59 if old_handler:
60 signal.signal(signal.SIGALRM, old_handler)
61 if old_alarm_sec > 0:
62 old_alarm_sec = int(old_timeout_time - time.time())
63 if old_alarm_sec <= 0:
64 old_alarm_sec = 1;
65 signal.alarm(old_alarm_sec)
66
67
Fang Deng241ae6c2013-05-01 11:43:28 -070068def retry(ExceptionToCheck, timeout_min=1.0, delay_sec=3, blacklist=None):
Chris Masone6f109082012-07-18 14:21:38 -070069 """Retry calling the decorated function using a delay with jitter.
70
71 Will raise RPC ValidationError exceptions from the decorated
72 function without retrying; a malformed RPC isn't going to
Fang Deng241ae6c2013-05-01 11:43:28 -070073 magically become good. Will raise exceptions in blacklist as well.
Chris Masone6f109082012-07-18 14:21:38 -070074
75 original from:
76 http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
77
78 @param ExceptionToCheck: the exception to check. May be a tuple of
79 exceptions to check.
80 @param timeout_min: timeout in minutes until giving up.
81 @param delay_sec: pre-jittered delay between retries in seconds. Actual
82 delays will be centered around this value, ranging up to
83 50% off this midpoint.
Fang Deng241ae6c2013-05-01 11:43:28 -070084 @param blacklist: a list of exceptions that will be raised without retrying
Chris Masone6f109082012-07-18 14:21:38 -070085 """
86 def deco_retry(func):
87 random.seed()
Dan Shi6d31f802013-01-11 14:46:12 -080088
89
90 def delay():
91 """
92 'Jitter' the delay, up to 50% in either direction.
93 """
94 random_delay = random.uniform(.5 * delay_sec, 1.5 * delay_sec)
95 logging.warning('Retrying in %f seconds...', random_delay)
96 time.sleep(random_delay)
97
98
Chris Masone6f109082012-07-18 14:21:38 -070099 def func_retry(*args, **kwargs):
100 deadline = time.time() + timeout_min * 60 # convert to seconds.
Dan Shi6d31f802013-01-11 14:46:12 -0800101 # Used to cache exception to be raised later.
102 exc_info = None
103 delayed_enabled = False
Fang Deng241ae6c2013-05-01 11:43:28 -0700104 exception_tuple = () if blacklist is None else tuple(blacklist)
Chris Masone6f109082012-07-18 14:21:38 -0700105 while time.time() < deadline:
Dan Shi6d31f802013-01-11 14:46:12 -0800106 if delayed_enabled:
107 delay()
108 else:
109 delayed_enabled = True
Chris Masone6f109082012-07-18 14:21:38 -0700110 try:
Dan Shi6d31f802013-01-11 14:46:12 -0800111 # Clear the cache
112 exc_info = None
113 is_timeout, result = timeout(func, args, kwargs,
114 timeout_min*60)
115 if not is_timeout:
116 return result
Fang Deng241ae6c2013-05-01 11:43:28 -0700117 except exception_tuple:
118 raise
Dan Shi6d31f802013-01-11 14:46:12 -0800119 except (error.CrosDynamicSuiteException,
120 proxy.ValidationError):
121 raise
122 except ExceptionToCheck as e:
123 logging.warning('%s(%s)', e.__class__, e)
124 # Cache the exception to be raised later.
125 exc_info = sys.exc_info()
126 # The call must have timed out or raised ExceptionToCheck.
127 if not exc_info:
128 raise TimeoutException('Call is timed out.')
129 # Raise the cached exception with original backtrace.
130 raise exc_info[0], exc_info[1], exc_info[2]
131
132
Chris Masone6f109082012-07-18 14:21:38 -0700133 return func_retry # true decorator
Fang Deng241ae6c2013-05-01 11:43:28 -0700134 return deco_retry