Skip to content

gh-135736: Fix asyncio.TaskGroup swallowing errors on GeneratorExit#154538

Open
sreehariannam wants to merge 1 commit into
python:mainfrom
sreehariannam:gh-135736-taskgroup-generatorexit
Open

gh-135736: Fix asyncio.TaskGroup swallowing errors on GeneratorExit#154538
sreehariannam wants to merge 1 commit into
python:mainfrom
sreehariannam:gh-135736-taskgroup-generatorexit

Conversation

@sreehariannam

Copy link
Copy Markdown

Fixes gh-135736.

Problem

When the body of an async with asyncio.TaskGroup() block exits via a bare GeneratorExit (for example, because it's inside an async generator that gets closed with aclose()), the GeneratorExit was wrapped in a BaseExceptionGroup like any other exception. Since callers of aclose()/athrow() only know how to handle GeneratorExit (or StopAsyncIteration) coming back out, this surfaced as a spurious, hard-to-debug ExceptionGroup: unhandled errors in a TaskGroup instead of a clean generator close.

Fix

TaskGroup._aexit() now treats a GeneratorExit raised by the async with body itself as a "base error" (like SystemExit/KeyboardInterrupt) and re-raises it directly instead of wrapping it, so aclose() sees the exception type it expects.

This is deliberately scoped to the body-exception path only. TaskGroup._is_base_error() itself (used separately in _on_task_done() for exceptions raised by child tasks) is untouched — a task raising GeneratorExit internally is not a "close this generator" signal and continues to be wrapped in the group as before (covered by a new test, test_taskgroup_20e).

Since only one exception can propagate out through GeneratorExit-closing APIs, any sibling task errors that would otherwise be silently discarded in this path are now reported via loop.call_exception_handler() instead of disappearing (test_taskgroup_20c).

Testing

Added four tests (test_taskgroup_20b-test_taskgroup_20e) covering: bare propagation, propagation alongside a failing sibling task (checking the exception handler is invoked), a faithful reproduction of the original report via a real async generator + aclose(), and the unchanged task-level behavior. Verified each new test fails without the fix by reverting it locally and re-running. Full test_taskgroups suite (126 tests) passes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GeneratorExit and asyncio.TaskGroup - missing from _is_base_error?

1 participant