You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
inconsistency in asyncio.Task between cancellation while running vs. cancellation immediately after it finishes #82429
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=<Date2019-10-01.16:01:15.373>created_at=<Date2019-09-22.08:45:24.028>labels= []
title='inconsistency in asyncio.Task between cancellation while running vs. cancellation immediately after it finishes'updated_at=<Date2019-10-01.16:01:15.372>user='https://github.com/njsmith'
Just noticed this while looking at the code to asyncio.Task.__step
asyncio Futures have two different error states: they can have an arbitrary exception set, or they can be marked as cancelled.
asyncio Tasks handle this by detecting what exception was raised by the task code: if it's a CancelledError, then the mark the Future as cancelled, and if it's anything else, they set that as the Future's exception.
There is also a special case handled inside the 'except StopIteration' clause in Task.__step. If we request that a Task be cancelled, but then the task exits normally before we have a chance to throw in a CancelledError, then we also want mark the Future as cancelled. BUT, this special case is handled incorrectly: it does Future.set_exception(CancelledError()), instead of Future.cancel(). Normally it's impossible for a task to end up with its exception set to CancelledError, but it does happen in this one weird edge case, basically as a race condition.
Here's some sample code to illustrate the problem (tested on 3.7):
------
importasyncio# This gets cancelled normallyasyncdefcancel_early():
asyncio.current_task().cancel()
awaitasyncio.sleep(1)
asyncdefcancel_late():
asyncio.current_task().cancel()
# No sleep, so CancelledError doesn't get delivered until after the# task exitsasyncdefmain():
t_early=asyncio.create_task(cancel_early())
awaitasyncio.sleep(0.1)
print(f"t_early.cancelled(): {t_early.cancelled()!r}")
t_late=asyncio.create_task(cancel_late())
awaitasyncio.sleep(0.1)
print(f"t_late.cancelled(): {t_late.cancelled()!r}")
print(f"t_late.exception(): {t_late.exception()!r}")
asyncio.run(main())
The obvious fix would be to modify the 'except StopIteration' branch to handle this case by calling super().cancel() instead of super().set_exception(...).
Alternatively, I could see an argument that asyncio.Task should always preserve the CancelledError, so that e.g. you can get a proper traceback. In that case we'd need to change the 'except CancelledError' branch to call super().set_exception(...) instead of super().cancel(). This would also need some more changes, like overriding .cancelled() to check for a stored exception and then return isinstance(stored_exc, CancelledError), and maybe others... I'm not sure of the full consequences.
But handling these two cases differently is definitely wrong, that part I'm sure of :-)
The obvious fix would be to modify the 'except StopIteration' branch to handle this case by calling super().cancel() instead of super().set_exception(...).
Yeah, I think this is the solution we should do in 3.8.
New changeset edad4d8 by Carol Willing (Yury Selivanov) in branch 'master': bpo-38248: Fix inconsistent immediate asyncio.Task cancellation (GH-16330) edad4d8
Activity
njsmith commentedon Sep 22, 2019
Just noticed this while looking at the code to asyncio.Task.__step
asyncio Futures have two different error states: they can have an arbitrary exception set, or they can be marked as cancelled.
asyncio Tasks handle this by detecting what exception was raised by the task code: if it's a CancelledError, then the mark the Future as cancelled, and if it's anything else, they set that as the Future's exception.
There is also a special case handled inside the 'except StopIteration' clause in Task.__step. If we request that a Task be cancelled, but then the task exits normally before we have a chance to throw in a CancelledError, then we also want mark the Future as cancelled. BUT, this special case is handled incorrectly: it does Future.set_exception(CancelledError()), instead of Future.cancel(). Normally it's impossible for a task to end up with its exception set to CancelledError, but it does happen in this one weird edge case, basically as a race condition.
Here's some sample code to illustrate the problem (tested on 3.7):
------
Output:
t_early.cancelled(): True
t_late.cancelled(): False
t_late.exception(): CancelledError()
The obvious fix would be to modify the 'except StopIteration' branch to handle this case by calling super().cancel() instead of super().set_exception(...).
Alternatively, I could see an argument that asyncio.Task should always preserve the CancelledError, so that e.g. you can get a proper traceback. In that case we'd need to change the 'except CancelledError' branch to call super().set_exception(...) instead of super().cancel(). This would also need some more changes, like overriding .cancelled() to check for a stored exception and then return isinstance(stored_exc, CancelledError), and maybe others... I'm not sure of the full consequences.
But handling these two cases differently is definitely wrong, that part I'm sure of :-)
1st1 commentedon Sep 22, 2019
Yeah, I think this is the solution we should do in 3.8.
willingc commentedon Sep 25, 2019
New changeset edad4d8 by Carol Willing (Yury Selivanov) in branch 'master':
bpo-38248: Fix inconsistent immediate asyncio.Task cancellation (GH-16330)
edad4d8
willingc commentedon Sep 25, 2019
New changeset 16cec13 by Carol Willing (Miss Islington (bot)) in branch '3.8':
bpo-38248: Fix inconsistent immediate asyncio.Task cancellation (GH-16330) (GH-16383)
16cec13
vstinner commentedon Sep 30, 2019
New changeset efe74b6 by Victor Stinner in branch 'master':
bpo-38321: Fix _asynciomodule.c compiler warning (GH-16493)
efe74b6
vstinner commentedon Oct 1, 2019
New changeset bfe1f74 by Victor Stinner in branch '3.8':
[3.8] bpo-3832: Fix compiler warnings (GH-16518)
bfe1f74
vstinner commentedon Oct 1, 2019
Can this issue be closed now?