4 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Utility functions for retrying function calls with a timeout.
28 from ganeti import errors
31 #: Special delay to specify whole remaining timeout
32 RETRY_REMAINING_TIME = object()
35 class RetryTimeout(Exception):
36 """Retry loop timed out.
38 Any arguments which was passed by the retried function to RetryAgain will be
39 preserved in RetryTimeout, if it is raised. If such argument was an exception
40 the RaiseInner helper method will reraise it.
44 if self.args and isinstance(self.args[0], Exception):
47 raise RetryTimeout(*self.args)
50 class RetryAgain(Exception):
53 Any arguments passed to RetryAgain will be preserved, if a timeout occurs, as
54 arguments to RetryTimeout. If an exception is passed, the RaiseInner() method
55 of the RetryTimeout() method can be used to reraise it.
60 class _RetryDelayCalculator(object):
61 """Calculator for increasing delays.
71 def __init__(self, start, factor, limit):
72 """Initializes this class.
75 @param start: Initial delay
77 @param factor: Factor for delay increase
78 @type limit: float or None
79 @param limit: Upper limit for delay or None for no limit
84 assert limit is None or limit >= 0.0
93 """Returns current delay and calculates the next one.
99 if self._limit is None or self._next < self._limit:
100 self._next = min(self._limit, self._next * self._factor)
105 def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
107 """Call a function repeatedly until it succeeds.
109 The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
110 anymore. Between calls a delay, specified by C{delay}, is inserted. After a
111 total of C{timeout} seconds, this function throws L{RetryTimeout}.
113 C{delay} can be one of the following:
114 - callable returning the delay length as a float
115 - Tuple of (start, factor, limit)
116 - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
117 useful when overriding L{wait_fn} to wait for an external event)
118 - A static delay as a number (int or float)
121 @param fn: Function to be called
122 @param delay: Either a callable (returning the delay), a tuple of (start,
123 factor, limit) (see L{_RetryDelayCalculator}),
124 L{RETRY_REMAINING_TIME} or a number (int or float)
126 @param timeout: Total timeout
127 @type wait_fn: callable
128 @param wait_fn: Waiting function
129 @return: Return value of function
133 assert callable(wait_fn)
134 assert callable(_time_fn)
139 end_time = _time_fn() + timeout
142 # External function to calculate delay
145 elif isinstance(delay, (tuple, list)):
146 # Increasing delay with optional upper boundary
147 (start, factor, limit) = delay
148 calc_delay = _RetryDelayCalculator(start, factor, limit)
150 elif delay is RETRY_REMAINING_TIME:
151 # Always use the remaining time
156 calc_delay = lambda: delay
158 assert calc_delay is None or callable(calc_delay)
163 # pylint: disable-msg=W0142
165 except RetryAgain, err:
166 retry_args = err.args
168 raise errors.ProgrammerError("Nested retry loop detected that didn't"
169 " handle RetryTimeout")
171 remaining_time = end_time - _time_fn()
173 if remaining_time < 0.0:
174 # pylint: disable-msg=W0142
175 raise RetryTimeout(*retry_args)
177 assert remaining_time >= 0.0
179 if calc_delay is None:
180 wait_fn(remaining_time)
182 current_delay = calc_delay()
183 if current_delay > 0.0:
184 wait_fn(current_delay)