|
1 | 1 | import asyncio
|
| 2 | +import sys |
2 | 3 | import time
|
3 | 4 |
|
4 | 5 | import pytest
|
5 | 6 |
|
6 |
| -from async_timeout import timeout, timeout_at |
| 7 | +from async_timeout import Timeout, timeout, timeout_at |
7 | 8 |
|
8 | 9 |
|
9 | 10 | @pytest.mark.asyncio
|
@@ -344,3 +345,55 @@ async def test_deprecated_with() -> None:
|
344 | 345 | with pytest.warns(DeprecationWarning):
|
345 | 346 | with timeout(1):
|
346 | 347 | await asyncio.sleep(0)
|
| 348 | + |
| 349 | + |
| 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 | + """ |
| 359 | + |
| 360 | + async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None: |
| 361 | + # We need the internal Timeout class to specify the deadline (not delay). |
| 362 | + # This is needed to create the precise timing to reproduce the race condition. |
| 363 | + with Timeout(deadline, loop): |
| 364 | + await asyncio.sleep(10) |
| 365 | + |
| 366 | + loop = asyncio.get_running_loop() |
| 367 | + deadline = loop.time() + 1 |
| 368 | + t = asyncio.create_task(test_task(deadline, loop)) |
| 369 | + loop.call_at(deadline, t.cancel) |
| 370 | + await asyncio.sleep(1.1) |
| 371 | + # If we get a TimeoutError, then the code is broken. |
| 372 | + with pytest.raises(asyncio.CancelledError): |
| 373 | + await t |
| 374 | + |
| 375 | + |
| 376 | +@pytest.mark.skipif(sys.version_info < (3, 7), reason="Not supported in 3.6") |
| 377 | +@pytest.mark.asyncio |
| 378 | +async def test_race_condition_cancel_after() -> None: |
| 379 | + """Test race condition when cancelling after timeout. |
| 380 | +
|
| 381 | + Similarly to the previous test, if a cancel happens |
| 382 | + immediately after the timeout (but before the __exit__), |
| 383 | + then the explicit cancel can get overruled again. |
| 384 | + """ |
| 385 | + |
| 386 | + async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None: |
| 387 | + # We need the internal Timeout class to specify the deadline (not delay). |
| 388 | + # This is needed to create the precise timing to reproduce the race condition. |
| 389 | + with Timeout(deadline, loop): |
| 390 | + await asyncio.sleep(10) |
| 391 | + |
| 392 | + loop = asyncio.get_running_loop() |
| 393 | + deadline = loop.time() + 1 |
| 394 | + t = asyncio.create_task(test_task(deadline, loop)) |
| 395 | + loop.call_at(deadline + 0.000001, t.cancel) |
| 396 | + await asyncio.sleep(1.1) |
| 397 | + # If we get a TimeoutError, then the code is broken. |
| 398 | + with pytest.raises(asyncio.CancelledError): |
| 399 | + await t |
0 commit comments