Skip to content

asyncio.wait leaks completed tasks via the 3.14 await-graph when racing a long-lived Future (missing future_discard_from_awaited_by) #152569

Description

@Skn0tt

Bug report

Bug description:

On Python 3.14, asyncio.wait(..., return_when=FIRST_COMPLETED) permanently retains every
completed waiting task when one of the awaited futures is long-lived (never resolves). Each call
registers the waiting task on the long-lived future's await-graph (_asyncio_awaited_by) but never
discards it once the wait returns, so the task is pinned until that future finally resolves — which,
for a "signal" future used as a cancellation/close marker, can mean the lifetime of the process.

This is the same omission already fixed for asyncio.staggered.staggered_race in
gh-129195 (add to awaited_by → must discard), but asyncio.wait still has it.

import asyncio, gc

async def main():
    loop = asyncio.get_running_loop()
    immortal = loop.create_future()  # never resolves (e.g. a connection-close signal)

    def done_tasks():
        gc.collect()
        return sum(1 for o in gc.get_objects() if isinstance(o, asyncio.Task) and o.done())

    async def race_once():
        await asyncio.wait(
            {asyncio.ensure_future(asyncio.sleep(0)), immortal},
            return_when=asyncio.FIRST_COMPLETED,
        )

    base = done_tasks()
    for _ in range(1000):
        await asyncio.create_task(race_once())
    print("retained done tasks:", done_tasks() - base)
    immortal.cancel()

asyncio.run(main())
3.13: retained done tasks: 1
3.14: retained done tasks: 1000   # grows linearly with calls; freed only when `immortal` resolves

Related

CPython versions tested on:

CPython main branch, 3.14

Operating systems tested on:

macOS

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixes3.15pre-release feature fixes, bugs and security fixes3.16new features, bugs and security fixesstdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error
    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions