| # Copyright 2016 Julien Danjou |
| # Copyright 2016 Joshua Harlow |
| # Copyright 2013-2014 Ray Holder |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import abc |
| import random |
| |
| import six |
| |
| from tenacity import _utils |
| |
| |
| @six.add_metaclass(abc.ABCMeta) |
| class wait_base(object): |
| """Abstract base class for wait strategies.""" |
| |
| @abc.abstractmethod |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| pass |
| |
| def __add__(self, other): |
| return wait_combine(self, other) |
| |
| def __radd__(self, other): |
| # make it possible to use multiple waits with the built-in sum function |
| if other == 0: |
| return self |
| return self.__add__(other) |
| |
| |
| class wait_fixed(wait_base): |
| """Wait strategy that waits a fixed amount of time between each retry.""" |
| |
| def __init__(self, wait): |
| self.wait_fixed = wait |
| |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| return self.wait_fixed |
| |
| |
| class wait_none(wait_fixed): |
| """Wait strategy that doesn't wait at all before retrying.""" |
| |
| def __init__(self): |
| super(wait_none, self).__init__(0) |
| |
| |
| class wait_random(wait_base): |
| """Wait strategy that waits a random amount of time between min/max.""" |
| |
| def __init__(self, min=0, max=1): # noqa |
| self.wait_random_min = min |
| self.wait_random_max = max |
| |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| return (self.wait_random_min + |
| (random.random() * |
| (self.wait_random_max - self.wait_random_min))) |
| |
| |
| class wait_combine(wait_base): |
| """Combine several waiting strategies.""" |
| |
| def __init__(self, *strategies): |
| self.wait_funcs = strategies |
| |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| return sum(map( |
| lambda x: x(previous_attempt_number, delay_since_first_attempt), |
| self.wait_funcs)) |
| |
| |
| class wait_chain(wait_base): |
| """Chain two or more waiting strategies. |
| |
| If all strategies are exhausted, the very last strategy is used |
| thereafter. |
| |
| For example:: |
| |
| @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] + |
| [wait_fixed(2) for j in range(5)] + |
| [wait_fixed(5) for k in range(4))) |
| def wait_chained(): |
| print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s |
| thereafter.") |
| """ |
| |
| def __init__(self, *strategies): |
| self.strategies = list(strategies) |
| |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| wait_func = self.strategies[0] |
| if len(self.strategies) > 1: |
| self.strategies.pop(0) |
| return wait_func(previous_attempt_number, delay_since_first_attempt) |
| |
| |
| class wait_incrementing(wait_base): |
| """Wait an incremental amount of time after each attempt. |
| |
| Starting at a starting value and incrementing by a value for each attempt |
| (and restricting the upper limit to some maximum value). |
| """ |
| |
| def __init__(self, start=0, increment=100, max=_utils.MAX_WAIT): # noqa |
| self.start = start |
| self.increment = increment |
| self.max = max |
| |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| result = self.start + ( |
| self.increment * (previous_attempt_number - 1) |
| ) |
| return max(0, min(result, self.max)) |
| |
| |
| class wait_exponential(wait_base): |
| """Wait strategy that applies exponential backoff. |
| |
| It allows for a customized multiplier and an ability to restrict the |
| upper limit to some maximum value. |
| |
| The intervals are fixed (i.e. there is no jitter), so this strategy is |
| suitable for balancing retries against latency when a required resource is |
| unavailable for an unknown duration, but *not* suitable for resolving |
| contention between multiple processes for a shared resource. Use |
| wait_random_exponential for the latter case. |
| """ |
| |
| def __init__(self, multiplier=1, max=_utils.MAX_WAIT, exp_base=2): # noqa |
| self.multiplier = multiplier |
| self.max = max |
| self.exp_base = exp_base |
| |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| try: |
| exp = self.exp_base ** previous_attempt_number |
| result = self.multiplier * exp |
| except OverflowError: |
| return self.max |
| return max(0, min(result, self.max)) |
| |
| |
| class wait_random_exponential(wait_exponential): |
| """Random wait with exponentially widening window. |
| |
| An exponential backoff strategy used to mediate contention between multiple |
| unco-ordinated processes for a shared resource in distributed systems. This |
| is the sense in which "exponential backoff" is meant in e.g. Ethernet |
| networking, and corresponds to the "Full Jitter" algorithm described in |
| this blog post: |
| |
| https://www.awsarchitectureblog.com/2015/03/backoff.html |
| |
| Each retry occurs at a random time in a geometrically expanding interval. |
| It allows for a custom multiplier and an ability to restrict the upper |
| limit of the random interval to some maximum value. |
| |
| Example:: |
| |
| wait_random_exponential(multiplier=0.5, # initial window 0.5s |
| max=60) # max 60s timeout |
| |
| When waiting for an unavailable resource to become available again, as |
| opposed to trying to resolve contention for a shared resource, the |
| wait_exponential strategy (which uses a fixed interval) may be preferable. |
| """ |
| |
| def __call__(self, previous_attempt_number, delay_since_first_attempt): |
| high = super(wait_random_exponential, self).__call__( |
| previous_attempt_number, delay_since_first_attempt) |
| return random.uniform(0, high) |