voussoirkit/voussoirkit/backoff.py

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
'''