@@ -82,6 +82,13 @@ def real_jitter():
8282 yield
8383
8484
85+ def failure_delay (state , kind = NORMAL ) -> float :
86+ """Records a failure and reads back the wait it decided, which is the value
87+ a data source reads."""
88+ state .record_failure (kind )
89+ return state .next_delay
90+
91+
8592def streaming_state (initial_delay = 1 ):
8693 return for_streaming (initial_delay )
8794
@@ -119,7 +126,7 @@ def test_a_non_positive_delay_falls_back_to_the_default(self, configured, caplog
119126 state = for_streaming (configured )
120127
121128 assert state .min_delay == DEFAULT_INITIAL_RECONNECT_DELAY
122- assert state . record_failure ( NORMAL ) == DEFAULT_INITIAL_RECONNECT_DELAY
129+ assert failure_delay ( state , NORMAL ) == DEFAULT_INITIAL_RECONNECT_DELAY
123130 assert caplog .records [0 ].getMessage () == (
124131 "initial_reconnect_delay must be greater than zero; using the default of 1s"
125132 )
@@ -131,7 +138,7 @@ def test_a_positive_delay_is_left_alone(self, configured, caplog):
131138 state = for_streaming (configured )
132139
133140 assert state .min_delay == configured
134- assert state . record_failure ( NORMAL ) == configured
141+ assert failure_delay ( state , NORMAL ) == configured
135142 assert caplog .records == []
136143
137144
@@ -144,12 +151,12 @@ class TestStreamingExtendedDelayFloor:
144151 [(1 , 300 ), (30 , 300 ), (300 , 300 ), (600 , 600 ), (3600 , 3600 ), (0 , 300 ), (- 5 , 300 )],
145152 )
146153 def test_the_extended_delay_never_starts_below_the_configured_delay (self , configured , expected ):
147- assert for_streaming (configured ). record_failure ( UNEXPECTED ) == expected
154+ assert failure_delay ( for_streaming (configured ), UNEXPECTED ) == expected
148155
149156 @pytest .mark .parametrize ("configured" , [1 , 30 , 300 , 600 , 3600 ])
150157 def test_an_unexpected_failure_never_waits_less_than_a_normal_one (self , configured ):
151- normal = for_streaming (configured ). record_failure ( NORMAL )
152- unexpected = for_streaming (configured ). record_failure ( UNEXPECTED )
158+ normal = failure_delay ( for_streaming (configured ), NORMAL )
159+ unexpected = failure_delay ( for_streaming (configured ), UNEXPECTED )
153160 assert unexpected >= normal
154161
155162 @pytest .mark .parametrize (
@@ -162,28 +169,28 @@ def test_an_unexpected_failure_never_waits_less_than_a_normal_one(self, configur
162169 )
163170 def test_the_extended_ladder_still_doubles_to_the_ceiling (self , configured , ladder ):
164171 state = for_streaming (configured )
165- delays = [state . record_failure ( UNEXPECTED )]
166- delays += [state . record_failure ( NORMAL ) for _ in range (4 )]
172+ delays = [failure_delay ( state , UNEXPECTED )]
173+ delays += [failure_delay ( state , NORMAL ) for _ in range (4 )]
167174 assert delays == ladder
168175
169176
170177class TestStreamingDelayTable :
171178 def test_normal_regime_doubles_up_to_the_ceiling (self ):
172179 state = streaming_state (initial_delay = 1 )
173- delays = [state . record_failure ( NORMAL ) for _ in range (8 )]
180+ delays = [failure_delay ( state , NORMAL ) for _ in range (8 )]
174181 assert delays == [1 , 2 , 4 , 8 , 16 , 30 , 30 , 30 ]
175182
176183 def test_extended_regime_doubles_up_to_the_ceiling (self ):
177184 state = streaming_state (initial_delay = 1 )
178- delays = [state . record_failure ( UNEXPECTED )]
179- delays += [state . record_failure ( NORMAL ) for _ in range (5 )]
185+ delays = [failure_delay ( state , UNEXPECTED )]
186+ delays += [failure_delay ( state , NORMAL ) for _ in range (5 )]
180187 assert delays == [5 * 60 , 10 * 60 , 20 * 60 , 40 * 60 , 60 * 60 , 60 * 60 ]
181188
182189 def test_a_configured_initial_delay_raises_the_ceiling_with_it (self ):
183190 # RETRY 1.5.4 as amended: maxDelay must not fall below initialDelay.
184191 state = streaming_state (initial_delay = 45 )
185192 assert state .max_delay == 45
186- assert state . record_failure ( NORMAL ) == 45
193+ assert failure_delay ( state , NORMAL ) == 45
187194
188195 def test_the_ceiling_is_sticky_once_the_extended_regime_starts (self ):
189196 # RETRY 1.5.5: a normal failure after an unexpected one must not lower
@@ -202,9 +209,9 @@ def test_a_second_unexpected_failure_keeps_counting_up(self):
202209 # Restarting the count on every unexpected failure would pin the delay
203210 # at the extended initial delay for ever.
204211 state = streaming_state (initial_delay = 1 )
205- assert state . record_failure ( UNEXPECTED ) == 5 * 60
206- assert state . record_failure ( UNEXPECTED ) == 10 * 60
207- assert state . record_failure ( UNEXPECTED ) == 20 * 60
212+ assert failure_delay ( state , UNEXPECTED ) == 5 * 60
213+ assert failure_delay ( state , UNEXPECTED ) == 10 * 60
214+ assert failure_delay ( state , UNEXPECTED ) == 20 * 60
208215
209216 def test_the_streaming_defaults_match_the_spec (self ):
210217 state = streaming_state (initial_delay = 1 )
@@ -217,20 +224,20 @@ class TestJitter:
217224 def test_jitter_never_removes_more_than_half_the_delay (self ):
218225 with fixed_retry_jitter (FULL_JITTER ):
219226 state = streaming_state (initial_delay = 8 )
220- delay = state . record_failure ( NORMAL )
227+ delay = failure_delay ( state , NORMAL )
221228 assert 4 <= delay < 8
222229
223230 def test_no_jitter_leaves_the_delay_alone (self ):
224231 state = streaming_state (initial_delay = 8 )
225- assert state . record_failure ( NORMAL ) == 8
232+ assert failure_delay ( state , NORMAL ) == 8
226233
227234 def test_every_delay_stays_within_the_jitter_bounds (self ):
228235 # The real random source, so the bound has to hold for any draw rather
229236 # than for one seeded sequence.
230237 with real_jitter ():
231238 state = streaming_state (initial_delay = 1 )
232239 for base in [1 , 2 , 4 , 8 , 16 , 30 , 30 , 30 ]:
233- delay = state . record_failure ( NORMAL )
240+ delay = failure_delay ( state , NORMAL )
234241 assert base / 2 <= delay <= base
235242
236243
@@ -249,7 +256,7 @@ def test_a_minute_of_healthy_operation_resets_the_state(self):
249256 state .record_success ()
250257 assert not state .in_extended_regime
251258 assert state .max_delay == STREAMING_MAX_DELAY
252- assert state . record_failure ( NORMAL ) == 1
259+ assert failure_delay ( state , NORMAL ) == 1
253260
254261 def test_a_reset_also_happens_on_the_failure_that_ends_a_healthy_stretch (self ):
255262 with frozen_clock () as clock :
@@ -262,7 +269,7 @@ def test_a_reset_also_happens_on_the_failure_that_ends_a_healthy_stretch(self):
262269
263270 # The state resets before this failure is counted, so the delay is
264271 # the first-retry delay again rather than the fourth.
265- assert state . record_failure ( NORMAL ) == 1
272+ assert failure_delay ( state , NORMAL ) == 1
266273
267274 def test_a_short_healthy_stretch_does_not_reset (self ):
268275 with frozen_clock () as clock :
@@ -271,7 +278,7 @@ def test_a_short_healthy_stretch_does_not_reset(self):
271278
272279 state .record_success ()
273280 clock .advance (STREAMING_RESET_INTERVAL - 1 )
274- assert state . record_failure ( NORMAL ) == 2
281+ assert failure_delay ( state , NORMAL ) == 2
275282
276283 def test_a_fast_flapping_connection_does_not_ratchet_into_the_extended_regime (self ):
277284 # Every transport failure is normal, so no amount of flapping reaches
@@ -283,7 +290,7 @@ def test_a_fast_flapping_connection_does_not_ratchet_into_the_extended_regime(se
283290 for _ in range (20 ):
284291 state .record_success ()
285292 clock .advance (5 )
286- delays .append (state . record_failure ( NORMAL ))
293+ delays .append (failure_delay ( state , NORMAL ))
287294 clock .advance (1 )
288295
289296 assert not state .in_extended_regime
@@ -294,26 +301,26 @@ def test_a_fast_flapping_connection_does_not_ratchet_into_the_extended_regime(se
294301class TestPollingCadence :
295302 def test_a_normal_failure_polls_again_on_schedule (self ):
296303 state = polling_state (poll_interval = 30 )
297- assert [state . record_failure ( NORMAL ) for _ in range (4 )] == [30 , 30 , 30 , 30 ]
304+ assert [failure_delay ( state , NORMAL ) for _ in range (4 )] == [30 , 30 , 30 , 30 ]
298305
299306 def test_the_extended_regime_doubles_up_to_an_hour (self ):
300307 state = polling_state (poll_interval = 30 )
301- delays = [state . record_failure ( UNEXPECTED )]
302- delays += [state . record_failure ( NORMAL ) for _ in range (5 )]
308+ delays = [failure_delay ( state , UNEXPECTED )]
309+ delays += [failure_delay ( state , NORMAL ) for _ in range (5 )]
303310 assert delays == [5 * 60 , 10 * 60 , 20 * 60 , 40 * 60 , 60 * 60 , 60 * 60 ]
304311
305312 def test_the_wait_never_falls_below_the_poll_interval (self ):
306313 # RETRY 1.4.9. Full jitter would otherwise halve the delay.
307314 with fixed_retry_jitter (FULL_JITTER ):
308315 state = polling_state (poll_interval = 30 )
309- assert state . record_failure ( NORMAL ) == 30
310- assert state . record_failure ( UNEXPECTED ) >= 30
316+ assert failure_delay ( state , NORMAL ) == 30
317+ assert failure_delay ( state , UNEXPECTED ) >= 30
311318
312319 def test_a_poll_interval_longer_than_the_extended_bounds_wins (self ):
313320 # The ceiling is lifted by record_failure clamping it against the
314321 # initial delay, not by for_polling clamping the ceiling itself.
315322 state = polling_state (poll_interval = 2 * 60 * 60 )
316- assert state . record_failure ( UNEXPECTED ) == 2 * 60 * 60
323+ assert failure_delay ( state , UNEXPECTED ) == 2 * 60 * 60
317324 assert state .max_delay == 2 * 60 * 60
318325 assert state .min_delay == 2 * 60 * 60
319326
@@ -324,7 +331,7 @@ def test_one_success_restores_the_cadence_while_the_state_is_still_raised(self):
324331 state = polling_state (poll_interval = 30 )
325332 state .record_failure (UNEXPECTED )
326333 state .record_failure (NORMAL )
327- assert state . record_failure ( NORMAL ) == 20 * 60
334+ assert failure_delay ( state , NORMAL ) == 20 * 60
328335
329336 state .record_success ()
330337 assert state .next_delay == 30
@@ -341,7 +348,7 @@ def test_two_successes_in_a_row_reset_the_state(self):
341348 state .record_success ()
342349 assert not state .in_extended_regime
343350 assert state .next_delay == 30
344- assert state . record_failure ( NORMAL ) == 30
351+ assert failure_delay ( state , NORMAL ) == 30
345352
346353 def test_a_failure_between_two_successes_clears_the_first (self ):
347354 state = polling_state (poll_interval = 30 )
@@ -384,7 +391,7 @@ def test_a_reset_starts_the_attempt_count_over(self):
384391
385392 # The delay drops back to the first-retry value, and the count
386393 # starts over with it.
387- assert state . record_failure ( NORMAL ) == 1
394+ assert failure_delay ( state , NORMAL ) == 1
388395 assert state .attempts == 1
389396
390397
@@ -460,5 +467,5 @@ def test_a_long_outage_cannot_overflow_the_delay(self):
460467 reset_policy = AfterHealthyFor (60 ),
461468 )
462469 for _ in range (5000 ):
463- delay = state . record_failure ( NORMAL )
470+ delay = failure_delay ( state , NORMAL )
464471 assert delay == 30
0 commit comments