-
Notifications
You must be signed in to change notification settings - Fork 59
Fix race conditions, support Python 3.11 #295
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Conversation
… iteration or task was cancelled just before timeout occurs
Python 3.11 tests fail, not released yet Python 3.11.0a6+ required |
If this fixes the same race conditions we were looking at before, can you put the tests back in from that discussion? Seems you reverted them for some reason: ab04eb5 |
Thanks, I lost these tests. |
It worked 100% of the times we tried it so far. If the last assert for |
I think this will still fail the second test. If the timeout cancellation is followed by the explicit cancellation, then the explicit cancellation is ignored. Without fixing this, it would require a user to do something awkward everytime they cancel a task, like:
I think we'll still need https://bugs.python.org/issue45098 |
@Dreamsorcerer you may be interested in CPython discussion about asyncio timeouts design: https://bugs.python.org/issue46771?@ok_message=msg%20413603%20created%0Aissue%2046771%20message_count%2C%20messages%20edited%20ok&@template=item Please feel free to join us. |
skip = False | ||
if sys.version_info >= (3, 9): | ||
# Analyse msg | ||
assert exc_val is not None | ||
if not exc_val.args or exc_val.args[0] != id(self): | ||
skip = True | ||
if not skip: | ||
if sys.version_info >= (3, 11): | ||
asyncio.current_task().uncancel() | ||
raise asyncio.TimeoutError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following what Guido was saying, I think this needs to check to check the cancelled count, maybe something like:
skip = False | |
if sys.version_info >= (3, 9): | |
# Analyse msg | |
assert exc_val is not None | |
if not exc_val.args or exc_val.args[0] != id(self): | |
skip = True | |
if not skip: | |
if sys.version_info >= (3, 11): | |
asyncio.current_task().uncancel() | |
raise asyncio.TimeoutError | |
self._timeout_handler = None | |
if sys.version_info >= (3, 11): | |
if asyncio.current_task().uncancel() == 0: | |
self._timeout_handler = None | |
raise asyncio.TimeoutError() | |
else: | |
raise asyncio.TimeoutError() |
I think this is all that's needed, as the == _State.TIMEOUT
tells us that we initiated a cancel, so there's no need for the cancel message anymore. Would be good to test this out against those previous tests before wrapping up the cpython discussion.
timeout_cm = timeout(3600) # reschedule just before the usage | ||
task2 = asyncio.create_task(main()) | ||
assert "ok" == await task2 | ||
assert timeout_cm.expired |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert timeout_cm.expired | |
assert timeout_cm.expired | |
async def race_condition(offset: float = 0) -> List[str]: | |
"""Common code for below race condition tests.""" | |
async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None: | |
# We need the internal Timeout class to specify the deadline (not delay). | |
# This is needed to create the precise timing to reproduce the race condition. | |
with pytest.warns(DeprecationWarning): | |
with Timeout(deadline, loop): | |
await asyncio.sleep(10) | |
call_order: List[str] = [] | |
f_exit = log_func(Timeout._do_exit, "exit", call_order) | |
Timeout._do_exit = f_exit # type: ignore[assignment] | |
f_timeout = log_func(Timeout._on_timeout, "timeout", call_order) | |
Timeout._on_timeout = f_timeout # type: ignore[assignment] | |
loop = asyncio.get_running_loop() | |
deadline = loop.time() + 1 | |
t = asyncio.create_task(test_task(deadline, loop)) | |
loop.call_at(deadline + offset, log_func(t.cancel, "cancel", call_order)) | |
# If we get a TimeoutError, then the code is broken. | |
with pytest.raises(asyncio.CancelledError): | |
await t | |
return call_order | |
@pytest.mark.skipif(sys.version_info < (3, 11), reason="Only fixed in 3.11+") | |
@pytest.mark.asyncio | |
async def test_race_condition_cancel_before() -> None: | |
"""Test race condition when cancelling before timeout. | |
If cancel happens immediately before the timeout, then | |
the timeout may overrule the cancellation, making it | |
impossible to cancel some tasks. | |
""" | |
call_order = await race_condition() | |
# This test is very timing dependant, so we check the order that calls | |
# happened to be sure the test itself ran correctly. | |
assert call_order == ["cancel", "timeout", "exit"] | |
@pytest.mark.skipif(sys.version_info < (3, 11), reason="Only fixed 3.11+.") | |
@pytest.mark.asyncio | |
async def test_race_condition_cancel_after() -> None: | |
"""Test race condition when cancelling after timeout. | |
Similarly to the previous test, if a cancel happens | |
immediately after the timeout (but before the __exit__), | |
then the explicit cancel can get overruled again. | |
""" | |
call_order = await race_condition(0.000001) | |
# This test is very timing dependant, so we check the order that calls | |
# happened to be sure the test itself ran correctly. | |
assert call_order == ["timeout", "cancel", "exit"] |
async_timeout does not support python 3.11 aio-libs/async-timeout#295 And have two years old annoying bugs: aio-libs/async-timeout#229 redis#2551 Since asyncio.timeout has been shipped in python 3.11, we should start using it. Partially fixes 2551
async_timeout does not support python 3.11 aio-libs/async-timeout#295 And have two years old annoying bugs: aio-libs/async-timeout#229 redis#2551 Since asyncio.timeout has been shipped in python 3.11, we should start using it. Partially fixes 2551
async_timeout does not support python 3.11 aio-libs/async-timeout#295 And have two years old annoying bugs: aio-libs/async-timeout#229 redis#2551 Since asyncio.timeout has been shipped in python 3.11, we should start using it. Partially fixes 2551
async_timeout does not support python 3.11 aio-libs/async-timeout#295 And have two years old annoying bugs: aio-libs/async-timeout#229 #2551 Since asyncio.timeout has been shipped in python 3.11, we should start using it. Partially fixes 2551
Version 5.0+ works exactly as |
No description provided.