https://github.com/python/cpython/commit/86e5a6e3eb2cb54176efdfd8ca7e76ae71a20acb
commit: 86e5a6e3eb2cb54176efdfd8ca7e76ae71a20acb
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-07-02T08:17:13Z
summary:

[3.14] gh-152569: Fix asyncio.wait leaking tasks via await-graph on long-lived 
futures (GH-152585) (#152866)

gh-152569: Fix asyncio.wait leaking tasks via await-graph on long-lived futures 
(GH-152585)
(cherry picked from commit f8514dccaca4acb84ba02188c3e433a9d6989c13)

Co-authored-by: Simon Knott <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-06-29-09-45-00.gh-issue-152569.Kf7Lq2.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 14d3c1ceb58cacb..9d20930dc300c67 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -524,6 +524,7 @@ def _on_completion(f):
             timeout_handle.cancel()
         for f in fs:
             f.remove_done_callback(_on_completion)
+            futures.future_discard_from_awaited_by(f, cur_task)
 
     done, pending = set(), set()
     for f in fs:
diff --git a/Lib/test/test_asyncio/test_tasks.py 
b/Lib/test/test_asyncio/test_tasks.py
index a3c5351fed02522..f084395b5c5b782 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -1218,6 +1218,19 @@ def gen():
         loop.advance_time(10)
         loop.run_until_complete(asyncio.wait([a, b]))
 
+    def test_wait_discards_awaited_by_for_pending(self):
+        # gh-152569: wait() must remove itself from the await-graph of every
+        # future once it returns, including futures that never resolved.
+        async def coro():
+            immortal = self.loop.create_future()
+            done = self.new_task(self.loop, asyncio.sleep(0))
+            await asyncio.wait({done, immortal},
+                               return_when=asyncio.FIRST_COMPLETED)
+            self.assertFalse(immortal._asyncio_awaited_by)
+            immortal.cancel()
+
+        self.loop.run_until_complete(self.new_task(self.loop, coro()))
+
     def test_wait_really_done(self):
         # there is possibility that some tasks in the pending list
         # became done but their callbacks haven't all been called yet
diff --git 
a/Misc/NEWS.d/next/Library/2026-06-29-09-45-00.gh-issue-152569.Kf7Lq2.rst 
b/Misc/NEWS.d/next/Library/2026-06-29-09-45-00.gh-issue-152569.Kf7Lq2.rst
new file mode 100644
index 000000000000000..fb7e32a5260b39a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-29-09-45-00.gh-issue-152569.Kf7Lq2.rst
@@ -0,0 +1,3 @@
+Fix :func:`asyncio.wait` leaking waiting tasks via the await-graph when racing 
a
+future that never resolves. The waiting task is now discarded from every 
future's
+``awaited_by`` set once :func:`~asyncio.wait` returns, even for pending 
futures.

_______________________________________________
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