103 lines
2.4 KiB
Python
103 lines
2.4 KiB
Python
'''
|
|
The Backoff classes are intended to be used to control `time.sleep` with
|
|
varying backoff strategies.
|
|
|
|
Example:
|
|
|
|
bo = backoff.Linear(m=1, b=1, max=30)
|
|
while True:
|
|
try:
|
|
something()
|
|
except Exception:
|
|
time.sleep(bo.next())
|
|
else:
|
|
bo.reset()
|
|
|
|
If you want to add random fuzziness to your sleeps, that should be done on the
|
|
calling end. For example, `bo.next() + (random.random() - 0.5)`.
|
|
'''
|
|
class Backoff:
|
|
def __init__(self, max):
|
|
self.x = 0
|
|
if max is None:
|
|
pass
|
|
elif max <= 0:
|
|
raise ValueError(f'max must be positive, not {max}.')
|
|
self.max = max
|
|
|
|
def current(self) -> float:
|
|
'''
|
|
Return the current backoff value without advancing.
|
|
'''
|
|
y = self._calc()
|
|
if self.max is not None:
|
|
y = min(y, self.max)
|
|
return y
|
|
|
|
def next(self) -> float:
|
|
'''
|
|
Return the current backoff value, then advance x.
|
|
'''
|
|
y = self.current()
|
|
self.x += 1
|
|
return y
|
|
|
|
def reset(self) -> None:
|
|
'''
|
|
Reset x to 0.
|
|
'''
|
|
self.x = 0
|
|
|
|
def rewind(self, steps) -> None:
|
|
'''
|
|
Subtract this many steps from x, to ease up the backoff without
|
|
entirely resetting.
|
|
'''
|
|
self.x = max(0, self.x - steps)
|
|
|
|
####################################################################################################
|
|
|
|
class Exponential(Backoff):
|
|
'''
|
|
Exponential backoff produces next = (a**x) + b.
|
|
'''
|
|
def __init__(self, a, b, *, max):
|
|
super().__init__(max)
|
|
self.a = a
|
|
self.b = b
|
|
self.max = max
|
|
|
|
def _calc(self):
|
|
return (self.a ** self.x) + self.b
|
|
|
|
class Linear(Backoff):
|
|
'''
|
|
Linear backoff produces next = (m * x) + b.
|
|
'''
|
|
def __init__(self, m, b, *, max):
|
|
super().__init__(max)
|
|
self.m = m
|
|
self.b = b
|
|
self.max = max
|
|
|
|
def _calc(self):
|
|
return (self.m * self.x) + self.b
|
|
|
|
class Quadratic(Backoff):
|
|
'''
|
|
Quadratic backoff produces next = (a * x**2) + (b * x) + c.
|
|
'''
|
|
def __init__(self, a, b, c, *, max):
|
|
super().__init__(max)
|
|
self.a = a
|
|
self.b = b
|
|
self.c = c
|
|
self.max = max
|
|
|
|
def _calc(self):
|
|
return (self.a * self.x**2) + (self.b * self.x) + self.c
|
|
|
|
'''
|
|
people are backing off
|
|
you'll get used to it
|
|
'''
|