Skip to content

gh-97907: use cancellation error from child if possible #133243

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,7 @@ def _done_callback(fut, cur_task=cur_task):
# All futures are done; create a list of results
# and set it to the 'outer' future.
results = []
cancelled_child = None

for fut in children:
if fut.cancelled():
Expand All @@ -853,6 +854,8 @@ def _done_callback(fut, cur_task=cur_task):
# to 'results' instead of raising it, don't bother
# setting __context__. This also lets us preserve
# calling '_make_cancelled_error()' at most once.
if cancelled_child is None:
cancelled_child = fut
res = exceptions.CancelledError(
'' if fut._cancel_message is None else
fut._cancel_message)
Expand All @@ -863,10 +866,15 @@ def _done_callback(fut, cur_task=cur_task):
results.append(res)

if outer._cancel_requested:
# If one or more children were cancelled, raise the exception
# from the first of those encountered, otherwise use the last
# child. See issue gh-97907.
if cancelled_child is None:
cancelled_child = fut
# If gather is being cancelled we must propagate the
# cancellation regardless of *return_exceptions* argument.
# See issue 32684.
exc = fut._make_cancelled_error()
exc = cancelled_child._make_cancelled_error()
outer.set_exception(exc)
else:
outer.set_result(results)
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,27 @@ async def main():
'raised by inner task to the gather() caller.'
)

def test_cancel_gather_3(self):
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)
barrier = asyncio.Barrier(2)

async def f1():
await barrier.wait()
await asyncio.sleep(1)

async def f2():
return 42

async def main():
gfut = asyncio.gather(f2(), f1(), f2(), return_exceptions=True)
await barrier.wait()
gfut.cancel("my message")
await gfut

with self.assertRaisesRegex(asyncio.CancelledError, "my message"):
loop.run_until_complete(main())

def test_exception_traceback(self):
# See http://bugs.python.org/issue28843

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Ensure cancellation error is raised by :func:`asyncio.gather` using the error
from a cancelled child, if there is one.
Loading