From fc5749ad88388ac5cc9298ef2f9070d494e58fb6 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Thu, 1 May 2025 18:52:37 +1200 Subject: [PATCH 1/3] gh-97907: use cancellation error from child if possible At present when cancelling the result of an :func:`asncio.gather` call, the last child is used to create the cancellation error. If that was not cancelled but another child was, its cancellation message and traceback will be lost. Fix this by using the cancellation error from the first of the children to be cancelled, if any, falling back to the last child only if none have been. --- Lib/asyncio/tasks.py | 10 +++++++++- Lib/test/test_asyncio/test_tasks.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 825e91f5594d98..058a2bfe3b86f6 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -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(): @@ -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) @@ -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) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 8d7f17334547b3..e6e3338612e80b 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -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 From 064435a02aad7f20b80eab081299c89b0a209658 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Thu, 1 May 2025 21:13:26 +1200 Subject: [PATCH 2/3] Add blurb --- .../next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst diff --git a/Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst b/Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst new file mode 100644 index 00000000000000..59c93de488a598 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst @@ -0,0 +1,2 @@ +Ensure cancellation error is raised by :func:`asncio.gather` using the error +from a cancelled child, if there is one. From 1150574c7795600dd2e9599f9a396a9b200f7303 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Thu, 1 May 2025 21:18:19 +1200 Subject: [PATCH 3/3] Fix typo --- .../next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst b/Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst index 59c93de488a598..cdc0b75d38373f 100644 --- a/Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst +++ b/Misc/NEWS.d/next/Library/2025-05-01-21-13-23.gh-issue-97907.MFyz5K.rst @@ -1,2 +1,2 @@ -Ensure cancellation error is raised by :func:`asncio.gather` using the error +Ensure cancellation error is raised by :func:`asyncio.gather` using the error from a cancelled child, if there is one.