python / python/cpython

asyncio eager task inherits the creators exception state

Open
#155,575 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stdlib topic-asyncio type-bug
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Bug report

Bug description:

Eager tasks created from an except block inherits the creator's exception state, a bare raise re-raises it, and any other exception gets it as __context__.

Repro:

import asyncio


async def bare():
    raise


async def explicit():
    raise ValueError("child's own")


async def main(coro):
    try:
        raise ConnectionError("primary failed")
    except ConnectionError:
        t = asyncio.create_task(coro)
    try:
        await t
    except BaseException as e:
        print(f"  {type(e).__name__}: {e} | __context__: {e.__context__!r}")


for eager in (False, True):
    print("eager_task_factory:", eager)
    for coro_fn in (bare, explicit):
        loop = asyncio.new_event_loop()
        if eager:
            loop.set_task_factory(asyncio.eager_task_factory)
        try:
            loop.run_until_complete(main(coro_fn()))
        finally:
            loop.close()

expected:

eager_task_factory: False
  RuntimeError: No active exception to reraise | __context__: None
  ValueError: child's own | __context__: None
eager_task_factory: True
  RuntimeError: No active exception to reraise | __context__: None
  ValueError: child's own | __context__: None

actually:

eager_task_factory: False
  RuntimeError: No active exception to reraise | __context__: None
  ValueError: child's own | __context__: None
eager_task_factory: True
  ConnectionError: primary failed | __context__: None
  ValueError: child's own | __context__: ConnectionError('primary failed')

proposed fix - run the eager first step under an empty exception state in task_eager_start

    _PyErr_StackItem exc_state = { .exc_value = NULL, .previous_item = NULL };
    _PyErr_StackItem *prev_exc_info = ts->base.exc_info;
    ts->base.exc_info = &exc_state;
    Py_BEGIN_CRITICAL_SECTION(task);
    stepres = task_step_impl(state, task, NULL);
    Py_END_CRITICAL_SECTION();
    ts->base.exc_info = prev_exc_info;

This error also affects python implementation of task, but i think this needs a discussion in a separate issue

Have a fix ready

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs
  • gh-155578

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the eager-task behavior in the issue's asyncio example and inspect the task_eager_start entry point. Compare eager and non-eager exception handling, then verify that the reported expected output is produced without changing the separate Python task implementation.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.