blob: 5aa1f81ff9c8c7f9734fee79c2f8485ef139b358 [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
68def retry(ExceptionToCheck, timeout_min=1.0, delay_sec=3):
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
73 magically become good.
74
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.
84 """
85 def deco_retry(func):
86 random.seed()
Dan Shi6d31f802013-01-11 14:46:12 -080087
88
89 def delay():
90 """
91 'Jitter' the delay, up to 50% in either direction.
92 """
93 random_delay = random.uniform(.5 * delay_sec, 1.5 * delay_sec)
94 logging.warning('Retrying in %f seconds...', random_delay)
95 time.sleep(random_delay)
96
97
Chris Masone6f109082012-07-18 14:21:38 -070098 def func_retry(*args, **kwargs):
99 deadline = time.time() + timeout_min * 60 # convert to seconds.
Dan Shi6d31f802013-01-11 14:46:12 -0800100 # Used to cache exception to be raised later.
101 exc_info = None
102 delayed_enabled = False
Chris Masone6f109082012-07-18 14:21:38 -0700103 while time.time() < deadline:
Dan Shi6d31f802013-01-11 14:46:12 -0800104 if delayed_enabled:
105 delay()
106 else:
107 delayed_enabled = True
Chris Masone6f109082012-07-18 14:21:38 -0700108 try:
Dan Shi6d31f802013-01-11 14:46:12 -0800109 # Clear the cache
110 exc_info = None
111 is_timeout, result = timeout(func, args, kwargs,
112 timeout_min*60)
113 if not is_timeout:
114 return result
115 except (error.CrosDynamicSuiteException,
116 proxy.ValidationError):
117 raise
118 except ExceptionToCheck as e:
119 logging.warning('%s(%s)', e.__class__, e)
120 # Cache the exception to be raised later.
121 exc_info = sys.exc_info()
122 # The call must have timed out or raised ExceptionToCheck.
123 if not exc_info:
124 raise TimeoutException('Call is timed out.')
125 # Raise the cached exception with original backtrace.
126 raise exc_info[0], exc_info[1], exc_info[2]
127
128
Chris Masone6f109082012-07-18 14:21:38 -0700129 return func_retry # true decorator
Dan Shi6d31f802013-01-11 14:46:12 -0800130 return deco_retry