wait.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. # Copyright 2016 Julien Danjou
  2. # Copyright 2016 Joshua Harlow
  3. # Copyright 2013-2014 Ray Holder
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import abc
  17. import random
  18. import six
  19. from tenacity import _utils
  20. from tenacity import compat as _compat
  21. @six.add_metaclass(abc.ABCMeta)
  22. class wait_base(object):
  23. """Abstract base class for wait strategies."""
  24. @abc.abstractmethod
  25. def __call__(self, retry_state):
  26. pass
  27. def __add__(self, other):
  28. return wait_combine(self, other)
  29. def __radd__(self, other):
  30. # make it possible to use multiple waits with the built-in sum function
  31. if other == 0:
  32. return self
  33. return self.__add__(other)
  34. class wait_fixed(wait_base):
  35. """Wait strategy that waits a fixed amount of time between each retry."""
  36. def __init__(self, wait):
  37. self.wait_fixed = wait
  38. @_compat.wait_dunder_call_accept_old_params
  39. def __call__(self, retry_state):
  40. return self.wait_fixed
  41. class wait_none(wait_fixed):
  42. """Wait strategy that doesn't wait at all before retrying."""
  43. def __init__(self):
  44. super(wait_none, self).__init__(0)
  45. class wait_random(wait_base):
  46. """Wait strategy that waits a random amount of time between min/max."""
  47. def __init__(self, min=0, max=1): # noqa
  48. self.wait_random_min = min
  49. self.wait_random_max = max
  50. @_compat.wait_dunder_call_accept_old_params
  51. def __call__(self, retry_state):
  52. return (self.wait_random_min +
  53. (random.random() *
  54. (self.wait_random_max - self.wait_random_min)))
  55. class wait_combine(wait_base):
  56. """Combine several waiting strategies."""
  57. def __init__(self, *strategies):
  58. self.wait_funcs = tuple(_compat.wait_func_accept_retry_state(strategy)
  59. for strategy in strategies)
  60. @_compat.wait_dunder_call_accept_old_params
  61. def __call__(self, retry_state):
  62. return sum(x(retry_state=retry_state) for x in self.wait_funcs)
  63. class wait_chain(wait_base):
  64. """Chain two or more waiting strategies.
  65. If all strategies are exhausted, the very last strategy is used
  66. thereafter.
  67. For example::
  68. @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] +
  69. [wait_fixed(2) for j in range(5)] +
  70. [wait_fixed(5) for k in range(4)))
  71. def wait_chained():
  72. print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s
  73. thereafter.")
  74. """
  75. def __init__(self, *strategies):
  76. self.strategies = [_compat.wait_func_accept_retry_state(strategy)
  77. for strategy in strategies]
  78. @_compat.wait_dunder_call_accept_old_params
  79. def __call__(self, retry_state):
  80. wait_func_no = min(max(retry_state.attempt_number, 1),
  81. len(self.strategies))
  82. wait_func = self.strategies[wait_func_no - 1]
  83. return wait_func(retry_state=retry_state)
  84. class wait_incrementing(wait_base):
  85. """Wait an incremental amount of time after each attempt.
  86. Starting at a starting value and incrementing by a value for each attempt
  87. (and restricting the upper limit to some maximum value).
  88. """
  89. def __init__(self, start=0, increment=100, max=_utils.MAX_WAIT): # noqa
  90. self.start = start
  91. self.increment = increment
  92. self.max = max
  93. @_compat.wait_dunder_call_accept_old_params
  94. def __call__(self, retry_state):
  95. result = self.start + (
  96. self.increment * (retry_state.attempt_number - 1)
  97. )
  98. return max(0, min(result, self.max))
  99. class wait_exponential(wait_base):
  100. """Wait strategy that applies exponential backoff.
  101. It allows for a customized multiplier and an ability to restrict the
  102. upper and lower limits to some maximum and minimum value.
  103. The intervals are fixed (i.e. there is no jitter), so this strategy is
  104. suitable for balancing retries against latency when a required resource is
  105. unavailable for an unknown duration, but *not* suitable for resolving
  106. contention between multiple processes for a shared resource. Use
  107. wait_random_exponential for the latter case.
  108. """
  109. def __init__(self, multiplier=1, max=_utils.MAX_WAIT, exp_base=2, min=0): # noqa
  110. self.multiplier = multiplier
  111. self.min = min
  112. self.max = max
  113. self.exp_base = exp_base
  114. @_compat.wait_dunder_call_accept_old_params
  115. def __call__(self, retry_state):
  116. try:
  117. exp = self.exp_base ** retry_state.attempt_number
  118. result = self.multiplier * exp
  119. except OverflowError:
  120. return self.max
  121. return max(max(0, self.min), min(result, self.max))
  122. class wait_random_exponential(wait_exponential):
  123. """Random wait with exponentially widening window.
  124. An exponential backoff strategy used to mediate contention between multiple
  125. unco-ordinated processes for a shared resource in distributed systems. This
  126. is the sense in which "exponential backoff" is meant in e.g. Ethernet
  127. networking, and corresponds to the "Full Jitter" algorithm described in
  128. this blog post:
  129. https://www.awsarchitectureblog.com/2015/03/backoff.html
  130. Each retry occurs at a random time in a geometrically expanding interval.
  131. It allows for a custom multiplier and an ability to restrict the upper
  132. limit of the random interval to some maximum value.
  133. Example::
  134. wait_random_exponential(multiplier=0.5, # initial window 0.5s
  135. max=60) # max 60s timeout
  136. When waiting for an unavailable resource to become available again, as
  137. opposed to trying to resolve contention for a shared resource, the
  138. wait_exponential strategy (which uses a fixed interval) may be preferable.
  139. """
  140. @_compat.wait_dunder_call_accept_old_params
  141. def __call__(self, retry_state):
  142. high = super(wait_random_exponential, self).__call__(
  143. retry_state=retry_state)
  144. return random.uniform(0, high)