gh-155143: Fix asyncio.shield() leaking tasks via await-graph (#155144)

kumaraditya303 <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/fbd2e015d462870b9a48bb5e6628af6c372ed1d5
commit: fbd2e015d462870b9a48bb5e6628af6c372ed1d5
branch: main
author: Andrew Geng <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-15T04:26:32Z
summary:

gh-155143: Fix asyncio.shield() leaking tasks via await-graph (#155144)

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 9d20930dc300c67..7889d4793a5dec3 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -997,6 +997,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 ad9b09857f8fd2b..9c111da8c27f162 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -2109,6 +2109,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()
@@ -2134,6 +2136,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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.