Skip to content

Commit

Permalink
Merge pull request #2938 from jakkdl/strict_internal_nursery_in_start
Browse files Browse the repository at this point in the history
Replace usage of strict_exception_groups=False in Nursery.start
  • Loading branch information
Zac-HD authored Jan 31, 2024
2 parents 4b6b497 + 03fee26 commit b6a31d7
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 13 deletions.
37 changes: 24 additions & 13 deletions src/trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -1227,19 +1227,30 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
raise RuntimeError("Nursery is closed to new arrivals")
try:
self._pending_starts += 1
# `strict_exception_groups=False` prevents the implementation-detail
# nursery from inheriting `strict_exception_groups=True` from the
# `run` option, which would cause it to wrap a pre-started()
# exception in an extra ExceptionGroup. See #2611.
async with open_nursery(strict_exception_groups=False) as old_nursery:
task_status: _TaskStatus[Any] = _TaskStatus(old_nursery, self)
thunk = functools.partial(async_fn, task_status=task_status)
task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
thunk, args, old_nursery, name
)
task._eventual_parent_nursery = self
# Wait for either TaskStatus.started or an exception to
# cancel this nursery:
# wrap internal nursery in try-except to unroll any exceptiongroups
# to avoid wrapping pre-started() exceptions in an extra ExceptionGroup.
# See #2611.
try:
# set strict_exception_groups = True to make sure we always unwrap
# *this* nursery's exceptiongroup
async with open_nursery(strict_exception_groups=True) as old_nursery:
task_status: _TaskStatus[Any] = _TaskStatus(old_nursery, self)
thunk = functools.partial(async_fn, task_status=task_status)
task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
thunk, args, old_nursery, name
)
task._eventual_parent_nursery = self
# Wait for either TaskStatus.started or an exception to
# cancel this nursery:
except BaseExceptionGroup as exc:
if len(exc.exceptions) == 1:
raise exc.exceptions[0] from None
raise TrioInternalError(
"Internal nursery should not have multiple tasks. This can be "
'caused by the user managing to access the "old" nursery in '
"`task_status` and spawning tasks in it."
) from exc

# If we get here, then the child either got reparented or exited
# normally. The complicated logic is all in TaskStatus.started().
# (Any exceptions propagate directly out of the above.)
Expand Down
16 changes: 16 additions & 0 deletions src/trio/_core/_tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2700,3 +2700,19 @@ async def start_raiser() -> None:
assert type(should_be_raiser_exc) == type(raiser_exc)
assert should_be_raiser_exc.message == raiser_exc.message
assert should_be_raiser_exc.exceptions == raiser_exc.exceptions


async def test_internal_error_old_nursery_multiple_tasks() -> None:
async def error_func() -> None:
raise ValueError

async def spawn_tasks_in_old_nursery(task_status: _core.TaskStatus[None]) -> None:
old_nursery = _core.current_task().parent_nursery
assert old_nursery is not None
old_nursery.start_soon(error_func)
old_nursery.start_soon(error_func)

async with _core.open_nursery() as nursery:
with pytest.raises(_core.TrioInternalError) as excinfo:
await nursery.start(spawn_tasks_in_old_nursery)
assert RaisesGroup(ValueError, ValueError).matches(excinfo.value.__cause__)

0 comments on commit b6a31d7

Please sign in to comment.