-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't raise if the task already have been cancelled explicitly #237
Conversation
I'll try to manage the red CI on evening. Tests pass locally. |
CI is failing because this unfortunately still does not fix the second case. A cancellation can still be suppressed. |
I feel like there needs to be something new introduced to asyncio, so when looking at the Maybe the args on the exception could contain every |
return | ||
task.cancel() | ||
self._state = _State.TIMEOUT | ||
self._loop.call_soon(self._cancel) |
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.
I find this much harder to reason about, I think it's more risky than the other solutions.
The reason this somewhat works is that it changes the call order in the before case to:
-> t.cancel()
-> Timeout._on_timeout()
-> Timeout._on_exit()
-> Timeout._cancel() # This is a noop, as it's already exited, so only 1 cancellation happens.
I'm not really sure that provides any guarantees (particularly when you have a more complex project with many tasks in flight), as it's just delaying the cancellation, which happens to be after the exit has already happened.
In the after case, this is not fixed at all (just like all the other attempts), in this case the execution looks like:
-> Timeout._on_timeout()
-> t.cancel()
-> Timeout._cancel() # Again, we've triggered 2 cancellations in a row
-> Timeout._do_exit() # No way to know there were 2 cancellations.
Raised this as a proposal: https://bugs.python.org/issue45098 |
@@ -396,7 +396,7 @@ async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None: | |||
loop = asyncio.get_running_loop() | |||
deadline = loop.time() + 1 | |||
t = asyncio.create_task(test_task(deadline, loop)) | |||
loop.call_at(deadline + 0.0000000000001, t.cancel) | |||
loop.call_at(deadline + 0.00000000001, t.cancel) |
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.
loop.call_at(deadline + 0.00000000001, t.cancel) | |
loop.call_at(deadline + 0.000001, t.cancel) |
I think you made the value so small that you've tricked your system into passing the tests by running the cancel() before the previous task. The value needs to be big enough to ensure the task is scheduled after the timeout task, but small enough that it happens immediately after without a chance to run the __exit__()
.
This test should be reliably failing. To verify the conditions, add prints to see when this cancel happens in relation to the timeout actions. The execution should be:
-> Timeout._on_timeout()
-> t.cancel()
-> Timeout._cancel()
-> Timeout._do_exit()
I suspect on your machine you've managed to change the execution order so t.cancel()
happens first, which is what the previous test is for.
Version 5.0+ wiorks exactly as |
Fixes #229