11import asyncio
22import sys
33import time
4+ from functools import wraps
5+ from typing import Any , Callable , List , TypeVar
46
57import pytest
68
79from async_timeout import Timeout , timeout , timeout_at
810
911
12+ _Func = TypeVar ("_Func" , bound = Callable [..., Any ])
13+
14+
15+ def log_func (func : _Func , msg : str , call_order : List [str ]) -> _Func :
16+ """Simple wrapper to add a log to call_order when the function is called."""
17+
18+ @wraps (func )
19+ def wrapper (* args : Any , ** kwargs : Any ) -> Any : # type: ignore[misc]
20+ call_order .append (msg )
21+ return func (* args , ** kwargs )
22+
23+ return wrapper # type: ignore[return-value]
24+
25+
1026@pytest .mark .asyncio
1127async def test_timeout () -> None :
1228 canceled_raised = False
@@ -347,15 +363,8 @@ async def test_deprecated_with() -> None:
347363 await asyncio .sleep (0 )
348364
349365
350- @pytest .mark .skipif (sys .version_info < (3 , 7 ), reason = "Not supported in 3.6" )
351- @pytest .mark .asyncio
352- async def test_race_condition_cancel_before () -> None :
353- """Test race condition when cancelling before timeout.
354-
355- If cancel happens immediately before the timeout, then
356- the timeout may overrule the cancellation, making it
357- impossible to cancel some tasks.
358- """
366+ async def race_condition (offset : float = 0 ) -> List [str ]:
367+ """Common code for below race condition tests."""
359368
360369 async def test_task (deadline : float , loop : asyncio .AbstractEventLoop ) -> None :
361370 # We need the internal Timeout class to specify the deadline (not delay).
@@ -364,39 +373,51 @@ async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None:
364373 with Timeout (deadline , loop ):
365374 await asyncio .sleep (10 )
366375
376+ call_order : List [str ] = []
377+ f_exit = log_func (Timeout ._do_exit , "exit" , call_order )
378+ Timeout ._do_exit = f_exit # type: ignore[assignment]
379+ f_timeout = log_func (Timeout ._on_timeout , "timeout" , call_order )
380+ Timeout ._on_timeout = f_timeout # type: ignore[assignment]
381+
367382 loop = asyncio .get_running_loop ()
368383 deadline = loop .time () + 1
369384 t = asyncio .create_task (test_task (deadline , loop ))
370- loop .call_at (deadline , t .cancel )
385+ loop .call_at (deadline + offset , log_func ( t .cancel , "cancel" , call_order ) )
371386 # If we get a TimeoutError, then the code is broken.
372387 with pytest .raises (asyncio .CancelledError ):
373388 await t
374389
390+ return call_order
391+
375392
376- @pytest .mark .xfail (
377- reason = "The test is CPU performance sensitive, might fail on slow CI box"
378- )
379393@pytest .mark .skipif (sys .version_info < (3 , 7 ), reason = "Not supported in 3.6" )
380394@pytest .mark .asyncio
395+ async def test_race_condition_cancel_before () -> None :
396+ """Test race condition when cancelling before timeout.
397+
398+ If cancel happens immediately before the timeout, then
399+ the timeout may overrule the cancellation, making it
400+ impossible to cancel some tasks.
401+ """
402+ call_order = await race_condition ()
403+
404+ # This test is very timing dependant, so we check the order that calls
405+ # happened to be sure the test itself ran correctly.
406+ assert call_order == ["cancel" , "timeout" , "exit" ]
407+
408+
409+ @pytest .mark .xfail (reason = "Can't see a way to fix this currently." )
410+ @pytest .mark .skipif (sys .version_info < (3 , 9 ), reason = "Can't be fixed in <3.9." )
411+ @pytest .mark .asyncio
381412async def test_race_condition_cancel_after () -> None :
382413 """Test race condition when cancelling after timeout.
383414
384415 Similarly to the previous test, if a cancel happens
385416 immediately after the timeout (but before the __exit__),
386417 then the explicit cancel can get overruled again.
387418 """
419+ call_order = await race_condition (0.000001 )
388420
389- async def test_task (deadline : float , loop : asyncio .AbstractEventLoop ) -> None :
390- # We need the internal Timeout class to specify the deadline (not delay).
391- # This is needed to create the precise timing to reproduce the race condition.
392- with pytest .warns (DeprecationWarning ):
393- with Timeout (deadline , loop ):
394- await asyncio .sleep (10 )
395-
396- loop = asyncio .get_running_loop ()
397- deadline = loop .time () + 1
398- t = asyncio .create_task (test_task (deadline , loop ))
399- loop .call_at (deadline + 0.0000000000001 , t .cancel )
400- # If we get a TimeoutError, then the code is broken.
401- with pytest .raises (asyncio .CancelledError ):
402- await t
421+ # This test is very timing dependant, so we check the order that calls
422+ # happened to be sure the test itself ran correctly.
423+ assert call_order == ["timeout" , "cancel" , "exit" ]
0 commit comments