Bug report
Bug description:
Consider the following code:
import asyncio
async def wait_and_raise():
await asyncio.sleep(0.5)
raise RuntimeError(1)
async def wait_and_start(tg):
try:
await asyncio.sleep(1)
finally:
try:
tg.create_task(asyncio.sleep(1))
except RuntimeError as e:
print(f"wait_and_start() caught {e!r}")
async def main():
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(wait_and_start(tg))
tg.create_task(wait_and_raise())
except Exception as e:
print(f"main() caught {e!r}")
try:
tg.create_task(asyncio.sleep(1))
except RuntimeError as e:
print(f"main() caught {e!r}")
asyncio.run(main())
This gives the following output
wait_and_start() caught RuntimeError('TaskGroup <TaskGroup tasks=1 errors=1 cancelling> is shutting down')
C:\code\taskgrouptest.py:16: RuntimeWarning: coroutine 'sleep' was never awaited
print(f"wait_and_start() caught {e!r}")
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
main() caught ExceptionGroup('unhandled errors in a TaskGroup', [RuntimeError(1)])
main() caught RuntimeError('TaskGroup <TaskGroup cancelling> is finished')
C:\code\taskgrouptest.py:29: RuntimeWarning: coroutine 'sleep' was never awaited
print(f"main() caught {e!r}")
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Arguably, when you call tg.create_task() on a task group that is shutting down or has finished, the calling code "knows" about the error because it gets a RuntimeError exception (as you can see above), so there is no need to get a warning about a coroutine that was not awaited. So, when a TaskGroup encounters this situation, it should close the coroutine before raising the error.
The other argument would be that this still represents a design mistake so should still get the warning. I can see both points of view but I'm raising this issue so a conscious decision can be made.
For comparison, when you do this on a Trio Nursery or AnyIO TaskGroup that has already closed, a coroutine never even gets created in the first place, because you use a different syntax (nursery.start_soon(foo, 1, 2) rather than tg.create_task(foo(1, 2))), so it's a lot like if asyncio were to close the coroutine. The situation is a bit different for a nursery that is shutting down: then it runs till the first (unshielded) await and is cancelled at that point, which is possible because they use level-based cancellation rather than edge-based cancellation.
CPython versions tested on:
3.12
Operating systems tested on:
Windows
Linked PRs
Bug report
Bug description:
Consider the following code:
This gives the following output
Arguably, when you call
tg.create_task()on a task group that is shutting down or has finished, the calling code "knows" about the error because it gets aRuntimeErrorexception (as you can see above), so there is no need to get a warning about a coroutine that was not awaited. So, when aTaskGroupencounters this situation, it should close the coroutine before raising the error.The other argument would be that this still represents a design mistake so should still get the warning. I can see both points of view but I'm raising this issue so a conscious decision can be made.
For comparison, when you do this on a Trio Nursery or AnyIO TaskGroup that has already closed, a coroutine never even gets created in the first place, because you use a different syntax (
nursery.start_soon(foo, 1, 2)rather thantg.create_task(foo(1, 2))), so it's a lot like if asyncio were to close the coroutine. The situation is a bit different for a nursery that is shutting down: then it runs till the first (unshielded)awaitand is cancelled at that point, which is possible because they use level-based cancellation rather than edge-based cancellation.CPython versions tested on:
3.12
Operating systems tested on:
Windows
Linked PRs