pteromys · GitHub

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

Read the original on github.com ↗