Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/redis/backoff.py: 47%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import random
2from abc import ABC, abstractmethod
4# Maximum backoff between each retry in seconds
5DEFAULT_CAP = 0.512
6# Minimum backoff between each retry in seconds
7DEFAULT_BASE = 0.008
10class AbstractBackoff(ABC):
11 """Backoff interface"""
13 def reset(self):
14 """
15 Reset internal state before an operation.
16 `reset` is called once at the beginning of
17 every call to `Retry.call_with_retry`
18 """
19 pass
21 @abstractmethod
22 def compute(self, failures: int) -> float:
23 """Compute backoff in seconds upon failure"""
24 pass
27class ConstantBackoff(AbstractBackoff):
28 """Constant backoff upon failure"""
30 def __init__(self, backoff: float) -> None:
31 """`backoff`: backoff time in seconds"""
32 self._backoff = backoff
34 def __hash__(self) -> int:
35 return hash((self._backoff,))
37 def __eq__(self, other) -> bool:
38 if not isinstance(other, ConstantBackoff):
39 return NotImplemented
41 return self._backoff == other._backoff
43 def compute(self, failures: int) -> float:
44 return self._backoff
47class NoBackoff(ConstantBackoff):
48 """No backoff upon failure"""
50 def __init__(self) -> None:
51 super().__init__(0)
54class ExponentialBackoff(AbstractBackoff):
55 """Exponential backoff upon failure"""
57 def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE):
58 """
59 `cap`: maximum backoff time in seconds
60 `base`: base backoff time in seconds
61 """
62 self._cap = cap
63 self._base = base
65 def __hash__(self) -> int:
66 return hash((self._base, self._cap))
68 def __eq__(self, other) -> bool:
69 if not isinstance(other, ExponentialBackoff):
70 return NotImplemented
72 return self._base == other._base and self._cap == other._cap
74 def compute(self, failures: int) -> float:
75 return min(self._cap, self._base * 2**failures)
78class FullJitterBackoff(AbstractBackoff):
79 """Full jitter backoff upon failure"""
81 def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
82 """
83 `cap`: maximum backoff time in seconds
84 `base`: base backoff time in seconds
85 """
86 self._cap = cap
87 self._base = base
89 def __hash__(self) -> int:
90 return hash((self._base, self._cap))
92 def __eq__(self, other) -> bool:
93 if not isinstance(other, FullJitterBackoff):
94 return NotImplemented
96 return self._base == other._base and self._cap == other._cap
98 def compute(self, failures: int) -> float:
99 return random.uniform(0, min(self._cap, self._base * 2**failures))
102class EqualJitterBackoff(AbstractBackoff):
103 """Equal jitter backoff upon failure"""
105 def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
106 """
107 `cap`: maximum backoff time in seconds
108 `base`: base backoff time in seconds
109 """
110 self._cap = cap
111 self._base = base
113 def __hash__(self) -> int:
114 return hash((self._base, self._cap))
116 def __eq__(self, other) -> bool:
117 if not isinstance(other, EqualJitterBackoff):
118 return NotImplemented
120 return self._base == other._base and self._cap == other._cap
122 def compute(self, failures: int) -> float:
123 temp = min(self._cap, self._base * 2**failures) / 2
124 return temp + random.uniform(0, temp)
127class DecorrelatedJitterBackoff(AbstractBackoff):
128 """Decorrelated jitter backoff upon failure"""
130 def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
131 """
132 `cap`: maximum backoff time in seconds
133 `base`: base backoff time in seconds
134 """
135 self._cap = cap
136 self._base = base
137 self._previous_backoff = 0
139 def __hash__(self) -> int:
140 return hash((self._base, self._cap))
142 def __eq__(self, other) -> bool:
143 if not isinstance(other, DecorrelatedJitterBackoff):
144 return NotImplemented
146 return self._base == other._base and self._cap == other._cap
148 def reset(self) -> None:
149 self._previous_backoff = 0
151 def compute(self, failures: int) -> float:
152 max_backoff = max(self._base, self._previous_backoff * 3)
153 temp = random.uniform(self._base, max_backoff)
154 self._previous_backoff = min(self._cap, temp)
155 return self._previous_backoff
158class ExponentialWithJitterBackoff(AbstractBackoff):
159 """Exponential backoff upon failure, with jitter"""
161 def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
162 """
163 `cap`: maximum backoff time in seconds
164 `base`: base backoff time in seconds
165 """
166 self._cap = cap
167 self._base = base
169 def __hash__(self) -> int:
170 return hash((self._base, self._cap))
172 def __eq__(self, other) -> bool:
173 if not isinstance(other, EqualJitterBackoff):
174 return NotImplemented
176 return self._base == other._base and self._cap == other._cap
178 def compute(self, failures: int) -> float:
179 return min(self._cap, random.random() * self._base * 2**failures)
182def default_backoff():
183 return EqualJitterBackoff()