diff --git a/newsfragments/3261.bugfix.rst b/newsfragments/3261.bugfix.rst new file mode 100644 index 0000000000..a5eb14117a --- /dev/null +++ b/newsfragments/3261.bugfix.rst @@ -0,0 +1,2 @@ +``Nursery.start()`` now preserves the ``__cause__`` and ``__context__`` of +exceptions raised before ``task_status.started()`` is called. diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py index 9ecdada1e0..24071d8ec8 100644 --- a/src/trio/_core/_run.py +++ b/src/trio/_core/_run.py @@ -33,7 +33,7 @@ from .. import _core from .._abc import Clock, Instrument from .._deprecate import warn_deprecated -from .._util import NoPublicConstructor, coroutine_or_error, final +from .._util import NoPublicConstructor, coroutine_or_error, final, raise_saving_context from ._asyncgens import AsyncGenerators from ._concat_tb import concat_tb from ._entry_queue import EntryQueue, TrioToken @@ -1466,7 +1466,7 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED): # cancel this nursery: except BaseExceptionGroup as exc: if len(exc.exceptions) == 1: - raise exc.exceptions[0] from None + raise_saving_context(exc.exceptions[0]) raise TrioInternalError( "Internal nursery should not have multiple tasks. This can be " 'caused by the user managing to access the "old" nursery in ' diff --git a/src/trio/_core/_tests/test_run.py b/src/trio/_core/_tests/test_run.py index 94e448448d..b8edc85fa1 100644 --- a/src/trio/_core/_tests/test_run.py +++ b/src/trio/_core/_tests/test_run.py @@ -2920,6 +2920,27 @@ async def start_raiser() -> None: assert should_be_raiser_exc.exceptions == raiser_exc.exceptions +async def test_start_exception_preserves_cause_and_context() -> None: + """Regression test for #3261: exceptions raised before task_status.started() + should preserve __cause__ and __context__.""" + + async def task(*, task_status: _core.TaskStatus[None]) -> None: + e = ValueError("foo") + e.__cause__ = SyntaxError("bar") + e.__context__ = TypeError("baz") + raise e + + with pytest.RaisesGroup( + pytest.RaisesExc( + ValueError, + check=lambda exc: isinstance(exc.__cause__, SyntaxError) + and isinstance(exc.__context__, TypeError), + ), + ): + async with _core.open_nursery() as nursery: + await nursery.start(task) + + async def test_internal_error_old_nursery_multiple_tasks() -> None: async def error_func() -> None: raise ValueError