Skip to content

Commit 8266352

Browse files
committed
Merge branch 'master' into micro-optimization
2 parents 00592cb + 9ea9f9b commit 8266352

File tree

5 files changed

+63
-40
lines changed

5 files changed

+63
-40
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
needs: lint
5858
strategy:
5959
matrix:
60-
pyver: [3.6, 3.7, 3.8, 3.9]
60+
pyver: ["3.6", "3.7", "3.8", "3.9", "3.10"]
6161
os: [ubuntu, macos, windows]
6262
include:
6363
- pyver: pypy3

.pre-commit-config.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ repos:
55
- id: check-merge-conflict
66
exclude: "rst$"
77
- repo: https://github.com/asottile/yesqa
8-
rev: v1.2.3
8+
rev: v1.3.0
99
hooks:
1010
- id: yesqa
11-
- repo: https://github.com/pre-commit/mirrors-isort
12-
rev: 'v5.9.3'
11+
- repo: https://github.com/PyCQA/isort
12+
rev: '5.9.3'
1313
hooks:
1414
- id: isort
1515
- repo: https://github.com/psf/black
16-
rev: '21.8b0'
16+
rev: '21.9b0'
1717
hooks:
1818
- id: black
1919
language_version: python3 # Should be a command that runs python3.6+
@@ -41,11 +41,11 @@ repos:
4141
files: |
4242
.gitignore
4343
- repo: https://github.com/asottile/pyupgrade
44-
rev: 'v2.25.0'
44+
rev: 'v2.29.0'
4545
hooks:
4646
- id: pyupgrade
4747
args: ['--py36-plus']
48-
- repo: https://gitlab.com/pycqa/flake8
49-
rev: '3.9.2'
48+
- repo: https://github.com/PyCQA/flake8
49+
rev: '4.0.1'
5050
hooks:
5151
- id: flake8

requirements.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
-e .
2-
black==21.8b0; implementation_name=="cpython"
3-
docutils==0.17.1
4-
flake8==3.9.2
2+
black==21.10b0; implementation_name=="cpython"
3+
docutils==0.18
4+
flake8==4.0.1
55
isort==5.9.3
66
mypy==0.910; implementation_name=="cpython"
77
pre-commit==2.15
88
pytest==6.2.5
9-
pytest-asyncio==0.15.1
10-
pytest-cov==2.12.1
9+
pytest-asyncio==0.16.0
10+
pytest-cov==3.0.0

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ def read(name):
3535
"License :: OSI Approved :: Apache Software License",
3636
"Intended Audience :: Developers",
3737
"Programming Language :: Python",
38+
"Programming Language :: Python :: 3 :: Only",
3839
"Programming Language :: Python :: 3",
3940
"Programming Language :: Python :: 3.6",
4041
"Programming Language :: Python :: 3.7",
4142
"Programming Language :: Python :: 3.8",
4243
"Programming Language :: Python :: 3.9",
44+
"Programming Language :: Python :: 3.10",
4345
"Topic :: Internet :: WWW/HTTP",
4446
"Framework :: AsyncIO",
4547
],

tests/test_timeout.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
import asyncio
22
import sys
33
import time
4+
from functools import wraps
5+
from typing import Any, Callable, List, TypeVar
46

57
import pytest
68

79
from 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
1127
async 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
381412
async 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

Comments
 (0)