11import asyncio
2+ import itertools
3+ from unittest .mock import patch
24
35import pytest
46
@@ -187,7 +189,8 @@ async def test_retry_delay_gets_reset_after_threshold():
187189 assert isinstance (item4 , Fault )
188190 assert client .next_retry_delay == initial_delay * 2
189191
190- await asyncio .sleep (retry_delay_reset_threshold )
192+ # Overshoot the threshold, because the clock can tick in ~15ms steps on Windows.
193+ await asyncio .sleep (retry_delay_reset_threshold * 1.5 )
191194
192195 item5 = await all_iter .__anext__ ()
193196 assert isinstance (item5 , Fault )
@@ -198,3 +201,71 @@ async def test_retry_delay_gets_reset_after_threshold():
198201 item6 = await all_iter .__anext__ ()
199202 assert isinstance (item6 , Fault )
200203 assert client .next_retry_delay == initial_delay * 2
204+
205+
206+ @pytest .mark .asyncio
207+ async def test_reconnect_delay_ignores_wall_clock_step ():
208+ initial_delay = 0.05
209+ mock = MockAsyncConnectStrategy (
210+ AsyncRejectConnection (HTTPStatusError (503 )),
211+ AsyncRespondWithData ("data: data1\n \n " ),
212+ AsyncExpectNoMoreRequests (),
213+ )
214+ real_sleep = asyncio .sleep
215+ delays = []
216+
217+ async def fake_sleep (delay , * args , ** kwargs ):
218+ delays .append (delay )
219+ await real_sleep (0 )
220+
221+ # Each wall-clock read steps back one hour.
222+ wall_clock = itertools .count (1_000_000_000 , - 3600 )
223+ with patch ('time.time' , side_effect = lambda : next (wall_clock )), patch (
224+ 'ld_eventsource.async_client.asyncio.sleep' , fake_sleep
225+ ):
226+ async with AsyncSSEClient (
227+ connect = mock ,
228+ error_strategy = retry_for_status (503 ),
229+ initial_retry_delay = initial_delay ,
230+ retry_delay_strategy = RetryDelayStrategy .default (jitter_multiplier = None ),
231+ ) as client :
232+ await client .start ()
233+
234+ assert len (delays ) == 1
235+ assert 0 < delays [0 ] <= initial_delay
236+
237+
238+ @pytest .mark .asyncio
239+ async def test_retry_delay_reset_ignores_wall_clock_step ():
240+ initial_delay = 0.005
241+ mock = MockAsyncConnectStrategy (
242+ AsyncRespondWithData ("data: data1\n \n " ),
243+ AsyncRejectConnection (HTTPStatusError (503 )),
244+ )
245+ real_sleep = asyncio .sleep
246+
247+ async def fake_sleep (delay , * args , ** kwargs ):
248+ await real_sleep (0 )
249+
250+ # Each wall-clock read steps forward one hour.
251+ wall_clock = itertools .count (1_000_000_000 , 3600 )
252+ with patch ('time.time' , side_effect = lambda : next (wall_clock )), patch (
253+ 'ld_eventsource.async_client.asyncio.sleep' , fake_sleep
254+ ):
255+ async with AsyncSSEClient (
256+ connect = mock ,
257+ error_strategy = ErrorStrategy .always_continue (),
258+ initial_retry_delay = initial_delay ,
259+ retry_delay_reset_threshold = 1000 ,
260+ retry_delay_strategy = RetryDelayStrategy .default (jitter_multiplier = None ),
261+ ) as client :
262+ all_iter = client .all .__aiter__ ()
263+ assert isinstance (await all_iter .__anext__ (), Start )
264+ assert isinstance (await all_iter .__anext__ (), Event )
265+ assert isinstance (await all_iter .__anext__ (), Fault )
266+ assert client .next_retry_delay == initial_delay
267+
268+ # The connection lasted far less than the threshold, so the
269+ # backoff must not reset.
270+ assert isinstance (await all_iter .__anext__ (), Fault )
271+ assert client .next_retry_delay == initial_delay * 2
0 commit comments