https://github.com/python/cpython/commit/9d05428b29508873fb26832eea5d17e0e8de60df
commit: 9d05428b29508873fb26832eea5d17e0e8de60df
branch: main
author: Timofei Ivankov <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-11-10T16:58:27+05:30
summary:

gh-161083: Fix stale awaited_by nodes on invalid asyncio.gather() arguments 
(#161084)

Co-authored-by: Kumar Aditya <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-11-01-19-38-33.gh-issue-161083.PM21lv.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 6c8d7cbbda64407..70da1e25ebaf081 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -926,8 +926,6 @@ def _done_callback(fut, cur_task=cur_task):
             if fut.done():
                 done_futs.append(fut)
             else:
-                if cur_task is not None:
-                    futures.future_add_to_awaited_by(fut, cur_task)
                 fut.add_done_callback(_done_callback)
 
         else:
@@ -937,6 +935,11 @@ def _done_callback(fut, cur_task=cur_task):
         children.append(fut)
 
     outer = _GatheringFuture(children, loop=loop)
+    # Add the awaited-by edges only after all arguments are validated
+    if cur_task is not None and len(done_futs) < nfuts:
+        for fut in arg_to_fut.values():
+            if not fut.done():
+                futures.future_add_to_awaited_by(fut, cur_task)
     # Run done callbacks after GatheringFuture created so any post-processing
     # can be performed at this point
     # optimization: in the special case that *all* futures finished eagerly,
diff --git a/Lib/test/test_asyncio/test_tasks.py 
b/Lib/test/test_asyncio/test_tasks.py
index f16815474e2d196..ac4856bd7a64c84 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -1328,6 +1328,18 @@ async def coro():
 
         self.loop.run_until_complete(self.new_task(self.loop, coro()))
 
+    def test_gather_invalid_arg_does_not_add_awaited_by(self):
+        async def coro():
+            task = self.new_task(self.loop, asyncio.sleep(0))
+            try:
+                with self.assertRaises(TypeError):
+                    asyncio.gather(task, None)
+                self.assertFalse(task._asyncio_awaited_by)
+            finally:
+                await task
+
+        self.loop.run_until_complete(self.new_task(self.loop, coro()))
+
     def test_gather_discards_awaited_by_for_pending(self):
         # gh-157213: a child outliving gather() must lose the awaited-by edge
         async def fail():
diff --git 
a/Misc/NEWS.d/next/Library/2026-11-01-19-38-33.gh-issue-161083.PM21lv.rst 
b/Misc/NEWS.d/next/Library/2026-11-01-19-38-33.gh-issue-161083.PM21lv.rst
new file mode 100644
index 000000000000000..98b3ea827276518
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-11-01-19-38-33.gh-issue-161083.PM21lv.rst
@@ -0,0 +1,2 @@
+Fix stale task nodes left by :func:`asyncio.gather` when argument validation
+fails. Patch by Timofei Ivankov

_______________________________________________
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