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:

importasyncioasyncdefrepro():
# create a long-running futurelong=asyncio.get_running_loop().create_future()
# create a future shielding itshielded=asyncio.shield(long)
print(len(long._asyncio_awaited_byor [])) # 1print([f.__name__forf, _inlong._callbacks]) # ['_clear_awaited_by_callback', '_inner_done_callback']# cancel and give callbacks a chance to runshielded.cancel()
awaitasyncio.sleep(0.)
# after this really only `_log_on_exception` should remain attached, but...print(len(long._asyncio_awaited_byor [])) # still 1 but I wish it was 0print([f.__name__forf, _inlong._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