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]

Reply via email to