1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15- """Helpers for retrying functions with exponential back-off."""
15+ """Helpers for retrying functions with exponential back-off.
16+
17+ The :cls:`Retry` decorator can be used to retry functions that raise exceptions
18+ using exponential backoff. Because a exponential sleep algorithm is used,
19+ the retry is limited by a `deadline`. The deadline is the maxmimum amount of
20+ time a method can block. This is used instead of total number of retries
21+ because it is difficult to ascertain the amount of time a function can block
22+ when using total number of retries and exponential backoff.
23+
24+ By default, this decorator will retry transient
25+ API errors (see :func:`if_transient_error`). For example:
26+
27+ .. code-block:: python
28+
29+ @retry.Retry()
30+ def call_flaky_rpc():
31+ return client.flaky_rpc()
32+
33+ # Will retry flaky_rpc() if it raises transient API errors.
34+ result = call_flaky_rpc()
35+
36+ You can pass a custom predicate to retry on different exceptions, such as
37+ waiting for an eventually consistent item to be available:
38+
39+ .. code-block:: python
40+
41+ @retry.Retry(predicate=if_exception_type(exceptions.NotFound))
42+ def check_if_exists():
43+ return client.does_thing_exist()
44+
45+ is_available = check_if_exists()
46+
47+ Some client library methods apply retry automatically. These methods can accept
48+ a ``retry`` parameter that allows you to configure the behavior:
49+
50+ .. code-block:: python
51+
52+ my_retry = retry.Retry(deadline=60)
53+ result = client.some_method(retry=my_retry)
54+
55+ """
56+
57+ from __future__ import unicode_literals
1658
1759import datetime
60+ import functools
1861import logging
1962import random
2063import time
2568from google .api .core .helpers import datetime_helpers
2669
2770_LOGGER = logging .getLogger (__name__ )
28- _DEFAULT_MAX_JITTER = 0.2
71+ _DEFAULT_INITIAL_DELAY = 1.0
72+ _DEFAULT_MAXIMUM_DELAY = 60.0
73+ _DEFAULT_DELAY_MULTIPLIER = 2.0
74+ _DEFAULT_DEADLINE = 60.0 * 2.0
2975
3076
3177def if_exception_type (* exception_types ):
@@ -38,10 +84,10 @@ def if_exception_type(*exception_types):
3884 Callable[Exception]: A predicate that returns True if the provided
3985 exception is of the given type(s).
4086 """
41- def inner (exception ):
87+ def if_exception_type_predicate (exception ):
4288 """Bound predicate for checking an exception type."""
4389 return isinstance (exception , exception_types )
44- return inner
90+ return if_exception_type_predicate
4591
4692
4793# pylint: disable=invalid-name
@@ -64,7 +110,7 @@ def inner(exception):
64110
65111
66112def exponential_sleep_generator (
67- initial , maximum , multiplier = 2 , jitter = _DEFAULT_MAX_JITTER ):
113+ initial , maximum , multiplier = _DEFAULT_DELAY_MULTIPLIER ):
68114 """Generates sleep intervals based on the exponential back-off algorithm.
69115
70116 This implements the `Truncated Exponential Back-off`_ algorithm.
@@ -77,16 +123,16 @@ def exponential_sleep_generator(
77123 be greater than 0.
78124 maximum (float): The maximum about of time to delay.
79125 multiplier (float): The multiplier applied to the delay.
80- jitter (float): The maximum about of randomness to apply to the delay.
81126
82127 Yields:
83128 float: successive sleep intervals.
84129 """
85130 delay = initial
86131 while True :
87- yield delay
88- delay = min (
89- delay * multiplier + random .uniform (0 , jitter ), maximum )
132+ # Introduce jitter by yielding a delay that is uniformly distributed
133+ # to average out to the delay time.
134+ yield min (random .uniform (0.0 , delay * 2.0 ), maximum )
135+ delay = delay * multiplier
90136
91137
92138def retry_target (target , predicate , sleep_generator , deadline ):
@@ -146,3 +192,120 @@ def retry_target(target, predicate, sleep_generator, deadline):
146192 time .sleep (sleep )
147193
148194 raise ValueError ('Sleep generator stopped yielding sleep values.' )
195+
196+
197+ @six .python_2_unicode_compatible
198+ class Retry (object ):
199+ """Exponential retry decorator.
200+
201+ This class is a decorator used to add exponential back-off retry behavior
202+ to an RPC call.
203+
204+ Although the default behavior is to retry transient API errors, a
205+ different predicate can be provided to retry other exceptions.
206+
207+ Args:
208+ predicate (Callable[Exception]): A callable that should return ``True``
209+ if the given exception is retryable.
210+ initial (float): The minimum about of time to delay in seconds. This
211+ must be greater than 0.
212+ maximum (float): The maximum about of time to delay in seconds.
213+ multiplier (float): The multiplier applied to the delay.
214+ deadline (float): How long to keep retrying in seconds.
215+ """
216+ def __init__ (
217+ self ,
218+ predicate = if_transient_error ,
219+ initial = _DEFAULT_INITIAL_DELAY ,
220+ maximum = _DEFAULT_MAXIMUM_DELAY ,
221+ multiplier = _DEFAULT_DELAY_MULTIPLIER ,
222+ deadline = _DEFAULT_DEADLINE ):
223+ self ._predicate = predicate
224+ self ._initial = initial
225+ self ._multiplier = multiplier
226+ self ._maximum = maximum
227+ self ._deadline = deadline
228+
229+ def __call__ (self , func ):
230+ """Wrap a callable with retry behavior.
231+
232+ Args:
233+ func (Callable): The callable to add retry behavior to.
234+
235+ Returns:
236+ Callable: A callable that will invoke ``func`` with retry
237+ behavior.
238+ """
239+ @six .wraps (func )
240+ def retry_wrapped_func (* args , ** kwargs ):
241+ """A wrapper that calls target function with retry."""
242+ target = functools .partial (func , * args , ** kwargs )
243+ sleep_generator = exponential_sleep_generator (
244+ self ._initial , self ._maximum , multiplier = self ._multiplier )
245+ return retry_target (
246+ target ,
247+ self ._predicate ,
248+ sleep_generator ,
249+ self ._deadline )
250+
251+ return retry_wrapped_func
252+
253+ def with_deadline (self , deadline ):
254+ """Return a copy of this retry with the given deadline.
255+
256+ Args:
257+ deadline (float): How long to keep retrying.
258+
259+ Returns:
260+ Retry: A new retry instance with the given deadline.
261+ """
262+ return Retry (
263+ predicate = self ._predicate ,
264+ initial = self ._initial ,
265+ maximum = self ._maximum ,
266+ multiplier = self ._multiplier ,
267+ deadline = deadline )
268+
269+ def with_predicate (self , predicate ):
270+ """Return a copy of this retry with the given predicate.
271+
272+ Args:
273+ predicate (Callable[Exception]): A callable that should return
274+ ``True`` if the given exception is retryable.
275+
276+ Returns:
277+ Retry: A new retry instance with the given predicate.
278+ """
279+ return Retry (
280+ predicate = predicate ,
281+ initial = self ._initial ,
282+ maximum = self ._maximum ,
283+ multiplier = self ._multiplier ,
284+ deadline = self ._deadline )
285+
286+ def with_delay (
287+ self , initial = None , maximum = None , multiplier = None ):
288+ """Return a copy of this retry with the given delay options.
289+
290+ Args:
291+ initial (float): The minimum about of time to delay. This must
292+ be greater than 0.
293+ maximum (float): The maximum about of time to delay.
294+ multiplier (float): The multiplier applied to the delay.
295+
296+ Returns:
297+ Retry: A new retry instance with the given predicate.
298+ """
299+ return Retry (
300+ predicate = self ._predicate ,
301+ initial = initial if initial is not None else self ._initial ,
302+ maximum = maximum if maximum is not None else self ._maximum ,
303+ multiplier = multiplier if maximum is not None else self ._multiplier ,
304+ deadline = self ._deadline )
305+
306+ def __str__ (self ):
307+ return (
308+ '<Retry predicate={}, initial={:.1f}, maximum={:.1f}, '
309+ 'multiplier={:.1f}, deadline={:.1f}>' .format (
310+ self ._predicate , self ._initial , self ._maximum ,
311+ self ._multiplier , self ._deadline ))
0 commit comments