Skip to content

asyncio.shield(long_lived_future) leaks a reference to the calling task #155143

Description

@pteromys

Bug report

Bug description:

This is basically gh-152569 but for asyncio.shield, and with two leakage mechanisms:

  1. The calling task (cur_task) is stored in inner._asyncio_awaited_by and not removed until inner is done.
  2. The calling task is captured by _clear_awaited_by_callback which lives in inner._callbacks until inner is done.

Reproducer:

import asyncio

async def repro():
    # create a long-running future
    long = asyncio.get_running_loop().create_future()
    # create a future shielding it
    shielded = asyncio.shield(long)
    print(len(long._asyncio_awaited_by or []))  # 1
    print([f.__name__ for f, _ in long._callbacks])  # ['_clear_awaited_by_callback', '_inner_done_callback']
    # cancel and give callbacks a chance to run
    shielded.cancel()
    await asyncio.sleep(0.)
    # after this really only `_log_on_exception` should remain attached, but...
    print(len(long._asyncio_awaited_by or []))  # still 1 but I wish it was 0
    print([f.__name__ for f, _ in long._callbacks])  # ['_clear_awaited_by_callback', '_log_on_exception']

asyncio.run(repro())

This is tested on 3.14.3, and I checked that the definition of asyncio.tasks.shield has not changed between that and current main (fd9feab at the time I'm writing this).

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions