Bug report
Bug description:
This is basically gh-152569 but for asyncio.shield, and with two leakage mechanisms:
- The calling task (
cur_task) is stored ininner._asyncio_awaited_byand not removed untilinneris done. - The calling task is captured by
_clear_awaited_by_callbackwhich lives ininner._callbacksuntilinneris 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