blob: c34ecb921d3660187729bee471562b8b4a12aa68 [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 Shi7b33c1a2012-12-21 11:34:52 -08005import logging, random, time, signal, sys
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 Shi7b33c1a2012-12-21 11:34:52 -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=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: default return value for the function call.
35 @return 1: is_timeout 2: result of the function call. If
36 is_timeout is True, the call is timed out. If the
37 value is False, the call is finished on time.
38 """
39 old_handler = signal.signal(signal.SIGALRM, handler)
40
41 timeout_sec_n = int(timeout_sec)
42 # In case the timeout is rounded to 0, force to set it to default value.
43 if timeout_sec_n == 0:
44 timeout_sec_n = 60
45 old_alarm_sec = signal.alarm(timeout_sec_n)
46 if old_alarm_sec > 0:
47 old_timeout_time = time.time() + old_alarm_sec
48 try:
49 result = func(*args, **kwargs)
50 # Cancel the timer if the function returned before timeout
51 signal.alarm(0)
52 return False, result
53 except TimeoutException:
54 return True, default
55 finally:
56 # Restore previous Signal handler and alarm
57 if old_handler is not None:
58 signal.signal(signal.SIGALRM, old_handler)
59 if old_alarm_sec > 0:
60 old_alarm_sec = int(old_timeout_time - time.time())
61 if old_alarm_sec <= 0:
62 old_alarm_sec = 1;
63 signal.alarm(old_alarm_sec)
64
65
66def retry(ExceptionToCheck, timeout_min=1.0, delay_sec=3):
Chris Masone6f109082012-07-18 14:21:38 -070067 """Retry calling the decorated function using a delay with jitter.
68
69 Will raise RPC ValidationError exceptions from the decorated
70 function without retrying; a malformed RPC isn't going to
71 magically become good.
72
73 original from:
74 http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
75
76 @param ExceptionToCheck: the exception to check. May be a tuple of
77 exceptions to check.
78 @param timeout_min: timeout in minutes until giving up.
79 @param delay_sec: pre-jittered delay between retries in seconds. Actual
80 delays will be centered around this value, ranging up to
81 50% off this midpoint.
82 """
83 def deco_retry(func):
84 random.seed()
Dan Shi7b33c1a2012-12-21 11:34:52 -080085
86
87 def delay():
88 """
89 'Jitter' the delay, up to 50% in either direction.
90 """
91 random_delay = random.uniform(.5 * delay_sec, 1.5 * delay_sec)
92 logging.warning("Retrying in %f seconds...", random_delay)
93 time.sleep(random_delay)
94
95
Chris Masone6f109082012-07-18 14:21:38 -070096 def func_retry(*args, **kwargs):
97 deadline = time.time() + timeout_min * 60 # convert to seconds.
Dan Shi7b33c1a2012-12-21 11:34:52 -080098 # Used to cache exception to be raised later.
99 exc_info = None
100 delayed_enabled = False
Chris Masone6f109082012-07-18 14:21:38 -0700101 while time.time() < deadline:
Dan Shi7b33c1a2012-12-21 11:34:52 -0800102 if delayed_enabled:
103 delay()
104 else:
105 delayed_enabled = True
Chris Masone6f109082012-07-18 14:21:38 -0700106 try:
Dan Shi7b33c1a2012-12-21 11:34:52 -0800107 # Clear the cache
108 exc_info = None
109 is_timeout, result = timeout(func, args, kwargs,
110 timeout_min*60)
111 if not is_timeout:
112 return result
113 except (error.CrosDynamicSuiteException,
114 proxy.ValidationError):
115 raise
116 except ExceptionToCheck as e:
117 logging.warning("%s(%s)", e.__class__, e)
118 # Cache the exception to be raised later.
119 exc_info = sys.exc_info()
120 # The call must have timed out or raised ExceptionToCheck.
121 if exc_info is None:
122 raise TimeoutException('Call is timed out.')
Chris Masone6f109082012-07-18 14:21:38 -0700123 else:
Dan Shi7b33c1a2012-12-21 11:34:52 -0800124 # Raise the cached exception with original backtrace.
125 raise exc_info[0], exc_info[1], exc_info[2]
126
127
Chris Masone6f109082012-07-18 14:21:38 -0700128 return func_retry # true decorator
129 return deco_retry