Open
Description
Bug report
Bug description:
Consider the following code:
import asyncio
async def TestGen():
yield 1
async def TestFn():
async with asyncio.TaskGroup() as tg:
async for n in TestGen():
yield n
async def Run():
g = TestFn()
await g.asend( None )
await g.aclose()
asyncio.run( Run() )
which unexpectedly yields the following output:
+ Exception Group Traceback (most recent call last):
| File "<python-input-7>", line 16, in <module>
| asyncio.run( Run() )
| ~~~~~~~~~~~^^^^^^^^^
| File "/usr/lib/python3.13/asyncio/runners.py", line 195, in run
| return runner.run(main)
| ~~~~~~~~~~^^^^^^
| File "/usr/lib/python3.13/asyncio/runners.py", line 118, in run
| return self._loop.run_until_complete(task)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
| File "/usr/lib/python3.13/asyncio/base_events.py", line 719, in run_until_complete
| return future.result()
| ~~~~~~~~~~~~~^^
| File "<python-input-7>", line 14, in Run
| await g.aclose()
| File "<python-input-7>", line 7, in TestFn
| async with asyncio.TaskGroup() as tg:
| ~~~~~~~~~~~~~~~~~^^
| File "/usr/lib/python3.13/asyncio/taskgroups.py", line 71, in __aexit__
| return await self._aexit(et, exc)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/lib/python3.13/asyncio/taskgroups.py", line 173, in _aexit
| raise BaseExceptionGroup(
| ...<2 lines>...
| ) from None
| BaseExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<python-input-7>", line 9, in TestFn
| yield n
| GeneratorExit
+------------------------------------
Expectation: no "unhandled errors in a TaskGroup" error.
Quick look through taskgroups.py
reveals that GeneratorExit
might be missing from _is_base_error()
method:
# Since Python 3.8 Tasks propagate all exceptions correctly,
# except for KeyboardInterrupt and SystemExit which are
# still considered special.
def _is_base_error(self, exc: BaseException) -> bool:
assert isinstance(exc, BaseException)
return isinstance(exc, (SystemExit, KeyboardInterrupt))
Adding GeneratorExit
to _is_base_error
seems to solve issue:
import asyncio
class TaskGroupWithGeneratorExit(asyncio.TaskGroup):
def _is_base_error(self, exc: BaseException) -> bool:
if super()._is_base_error( exc ):
return True
return isinstance(exc, GeneratorExit)
async def TestGen():
yield 1
async def TestFn():
async with TaskGroupWithGeneratorExit() as tg:
async for n in TestGen():
yield n
async def Run():
g = TestFn()
await g.asend( None )
await g.aclose()
asyncio.run( Run() )
The above yields no output, as expected.
CPython versions tested on:
3.13
Operating systems tested on:
Linux
Metadata
Metadata
Assignees
Projects
Status
Todo