Skip to content

Commit 86e5a6e

Browse files
[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 f8514dc) Co-authored-by: Simon Knott <info@simonknott.de>
1 parent c8663d3 commit 86e5a6e

3 files changed

Lines changed: 17 additions & 0 deletions

File tree

Lib/asyncio/tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ def _on_completion(f):
524524
timeout_handle.cancel()
525525
for f in fs:
526526
f.remove_done_callback(_on_completion)
527+
futures.future_discard_from_awaited_by(f, cur_task)
527528

528529
done, pending = set(), set()
529530
for f in fs:

Lib/test/test_asyncio/test_tasks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,19 @@ def gen():
12181218
loop.advance_time(10)
12191219
loop.run_until_complete(asyncio.wait([a, b]))
12201220

1221+
def test_wait_discards_awaited_by_for_pending(self):
1222+
# gh-152569: wait() must remove itself from the await-graph of every
1223+
# future once it returns, including futures that never resolved.
1224+
async def coro():
1225+
immortal = self.loop.create_future()
1226+
done = self.new_task(self.loop, asyncio.sleep(0))
1227+
await asyncio.wait({done, immortal},
1228+
return_when=asyncio.FIRST_COMPLETED)
1229+
self.assertFalse(immortal._asyncio_awaited_by)
1230+
immortal.cancel()
1231+
1232+
self.loop.run_until_complete(self.new_task(self.loop, coro()))
1233+
12211234
def test_wait_really_done(self):
12221235
# there is possibility that some tasks in the pending list
12231236
# became done but their callbacks haven't all been called yet
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`asyncio.wait` leaking waiting tasks via the await-graph when racing a
2+
future that never resolves. The waiting task is now discarded from every future's
3+
``awaited_by`` set once :func:`~asyncio.wait` returns, even for pending futures.

0 commit comments

Comments
 (0)