https://github.com/python/cpython/commit/4e0cffb32c2095c7259b38a8a683166569eb9889 commit: 4e0cffb32c2095c7259b38a8a683166569eb9889 branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 <[email protected]> date: 2026-09-11T19:05:41+05:30 summary:
[3.14] gh-155143: Fix asyncio.shield() leaking tasks via await-graph (GH-155144) (#157319) gh-155143: Fix asyncio.shield() leaking tasks via await-graph (GH-155144) (cherry picked from commit fbd2e015d462870b9a48bb5e6628af6c372ed1d5) Co-authored-by: Andrew Geng <[email protected]> files: A Misc/NEWS.d/next/Library/2026-08-03-22-46-07.gh-issue-155143.9-byZp.rst M Lib/asyncio/tasks.py M Lib/test/test_asyncio/test_tasks.py diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index e989498d6e8632b..f432cf0afa895a2 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -999,6 +999,9 @@ def _outer_done_callback(outer): # Keep only one callback to log on cancel inner.remove_done_callback(_log_on_exception) inner.add_done_callback(_log_on_exception) + if cur_task is not None: + inner.remove_done_callback(_clear_awaited_by_callback) + futures.future_discard_from_awaited_by(inner, cur_task) if cur_task is not None: inner.add_done_callback(_clear_awaited_by_callback) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index f6eea7fde6fac2b..f6f52107ab49145 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -2128,6 +2128,8 @@ def test_shield_cancel_outer(self): test_utils.run_briefly(self.loop) self.assertTrue(outer.cancelled()) self.assertEqual(0, 0 if outer._callbacks is None else len(outer._callbacks)) + self.assertFalse(inner._asyncio_awaited_by) + self.assertTrue({f for f, _ctx in inner._callbacks or []} <= {asyncio.tasks._log_on_exception}) def test_shield_cancel_outer_result(self): mock_handler = mock.Mock() @@ -2153,6 +2155,21 @@ def test_shield_cancel_outer_exception(self): test_utils.run_briefly(self.loop) mock_handler.assert_called_once() + def test_shield_cancel_outer_in_task(self): + inner = self.new_future(self.loop) + + async def coro(): + outer = asyncio.shield(inner) + self.assertNotEqual(0, len(inner._callbacks)) + outer.cancel() + await asyncio.sleep(0) + self.assertTrue(outer.cancelled()) + + task = self.new_task(self.loop, coro()) + self.loop.run_until_complete(task) + self.assertFalse(inner._asyncio_awaited_by) + self.assertTrue({f for f, _ctx in inner._callbacks or []} <= {asyncio.tasks._log_on_exception}) + def test_shield_duplicate_log_once(self): mock_handler = mock.Mock() self.loop.set_exception_handler(mock_handler) diff --git a/Misc/NEWS.d/next/Library/2026-08-03-22-46-07.gh-issue-155143.9-byZp.rst b/Misc/NEWS.d/next/Library/2026-08-03-22-46-07.gh-issue-155143.9-byZp.rst new file mode 100644 index 000000000000000..ae39b30e40a643e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-03-22-46-07.gh-issue-155143.9-byZp.rst @@ -0,0 +1,2 @@ +Fix :func:`asyncio.shield` leaking the calling task via the await-graph and +callbacks when called on a future that never resolves. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
