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

93 statements  

1import random 

2from abc import ABC, abstractmethod 

3 

4# Maximum backoff between each retry in seconds 

5DEFAULT_CAP = 0.512 

6# Minimum backoff between each retry in seconds 

7DEFAULT_BASE = 0.008 

8 

9 

10class AbstractBackoff(ABC): 

11 """Backoff interface""" 

12 

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 

20 

21 @abstractmethod 

22 def compute(self, failures: int) -> float: 

23 """Compute backoff in seconds upon failure""" 

24 pass 

25 

26 

27class ConstantBackoff(AbstractBackoff): 

28 """Constant backoff upon failure""" 

29 

30 def __init__(self, backoff: float) -> None: 

31 """`backoff`: backoff time in seconds""" 

32 self._backoff = backoff 

33 

34 def __hash__(self) -> int: 

35 return hash((self._backoff,)) 

36 

37 def __eq__(self, other) -> bool: 

38 if not isinstance(other, ConstantBackoff): 

39 return NotImplemented 

40 

41 return self._backoff == other._backoff 

42 

43 def compute(self, failures: int) -> float: 

44 return self._backoff 

45 

46 

47class NoBackoff(ConstantBackoff): 

48 """No backoff upon failure""" 

49 

50 def __init__(self) -> None: 

51 super().__init__(0) 

52 

53 

54class ExponentialBackoff(AbstractBackoff): 

55 """Exponential backoff upon failure""" 

56 

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 

64 

65 def __hash__(self) -> int: 

66 return hash((self._base, self._cap)) 

67 

68 def __eq__(self, other) -> bool: 

69 if not isinstance(other, ExponentialBackoff): 

70 return NotImplemented 

71 

72 return self._base == other._base and self._cap == other._cap 

73 

74 def compute(self, failures: int) -> float: 

75 return min(self._cap, self._base * 2**failures) 

76 

77 

78class FullJitterBackoff(AbstractBackoff): 

79 """Full jitter backoff upon failure""" 

80 

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 

88 

89 def __hash__(self) -> int: 

90 return hash((self._base, self._cap)) 

91 

92 def __eq__(self, other) -> bool: 

93 if not isinstance(other, FullJitterBackoff): 

94 return NotImplemented 

95 

96 return self._base == other._base and self._cap == other._cap 

97 

98 def compute(self, failures: int) -> float: 

99 return random.uniform(0, min(self._cap, self._base * 2**failures)) 

100 

101 

102class EqualJitterBackoff(AbstractBackoff): 

103 """Equal jitter backoff upon failure""" 

104 

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 

112 

113 def __hash__(self) -> int: 

114 return hash((self._base, self._cap)) 

115 

116 def __eq__(self, other) -> bool: 

117 if not isinstance(other, EqualJitterBackoff): 

118 return NotImplemented 

119 

120 return self._base == other._base and self._cap == other._cap 

121 

122 def compute(self, failures: int) -> float: 

123 temp = min(self._cap, self._base * 2**failures) / 2 

124 return temp + random.uniform(0, temp) 

125 

126 

127class DecorrelatedJitterBackoff(AbstractBackoff): 

128 """Decorrelated jitter backoff upon failure""" 

129 

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 

138 

139 def __hash__(self) -> int: 

140 return hash((self._base, self._cap)) 

141 

142 def __eq__(self, other) -> bool: 

143 if not isinstance(other, DecorrelatedJitterBackoff): 

144 return NotImplemented 

145 

146 return self._base == other._base and self._cap == other._cap 

147 

148 def reset(self) -> None: 

149 self._previous_backoff = 0 

150 

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 

156 

157 

158class ExponentialWithJitterBackoff(AbstractBackoff): 

159 """Exponential backoff upon failure, with jitter""" 

160 

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 

168 

169 def __hash__(self) -> int: 

170 return hash((self._base, self._cap)) 

171 

172 def __eq__(self, other) -> bool: 

173 if not isinstance(other, EqualJitterBackoff): 

174 return NotImplemented 

175 

176 return self._base == other._base and self._cap == other._cap 

177 

178 def compute(self, failures: int) -> float: 

179 return min(self._cap, random.random() * self._base * 2**failures) 

180 

181 

182def default_backoff(): 

183 return EqualJitterBackoff()