blob: 1a503dc0eabdf4c0af85294a0c5799cccaafa9a8 [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 Shi76b921e2012-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 Shi76b921e2012-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 Shi76b921e2012-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 Shi76b921e2012-12-21 11:34:52 -080098 # Used to cache exception to be raised later.
99 exc_info = None
Chris Masone6f109082012-07-18 14:21:38 -0700100 while time.time() < deadline:
101 try:
Dan Shi76b921e2012-12-21 11:34:52 -0800102 # Clear the cache
103 exc_info = None
104 is_timeout, result = timeout(func, args, kwargs,
105 timeout_min*60)
106 if is_timeout:
107 delay()
108 else:
109 return result
110 except (error.CrosDynamicSuiteException,
111 proxy.ValidationError):
112 raise
113 except ExceptionToCheck as e:
114 logging.warning("%s(%s)", e.__class__, e)
115 # Cache the exception to be raised later.
116 exc_info = sys.exc_info()
117 delay()
118 # The call must have timed out or raised ExceptionToCheck.
119 if exc_info is None:
120 raise TimeoutException('Call is timed out.')
Chris Masone6f109082012-07-18 14:21:38 -0700121 else:
Dan Shi76b921e2012-12-21 11:34:52 -0800122 # Raise the cached exception with original backtrace.
123 raise exc_info[0], exc_info[1], exc_info[2]
124
125
Chris Masone6f109082012-07-18 14:21:38 -0700126 return func_retry # true decorator
127 return deco_retry