Skip to content

Commit b6a31d7

Browse files
authored
Merge pull request #2938 from jakkdl/strict_internal_nursery_in_start
Replace usage of strict_exception_groups=False in Nursery.start
2 parents 4b6b497 + 03fee26 commit b6a31d7

File tree

2 files changed

+40
-13
lines changed

2 files changed

+40
-13
lines changed

src/trio/_core/_run.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,19 +1227,30 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
12271227
raise RuntimeError("Nursery is closed to new arrivals")
12281228
try:
12291229
self._pending_starts += 1
1230-
# `strict_exception_groups=False` prevents the implementation-detail
1231-
# nursery from inheriting `strict_exception_groups=True` from the
1232-
# `run` option, which would cause it to wrap a pre-started()
1233-
# exception in an extra ExceptionGroup. See #2611.
1234-
async with open_nursery(strict_exception_groups=False) as old_nursery:
1235-
task_status: _TaskStatus[Any] = _TaskStatus(old_nursery, self)
1236-
thunk = functools.partial(async_fn, task_status=task_status)
1237-
task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
1238-
thunk, args, old_nursery, name
1239-
)
1240-
task._eventual_parent_nursery = self
1241-
# Wait for either TaskStatus.started or an exception to
1242-
# cancel this nursery:
1230+
# wrap internal nursery in try-except to unroll any exceptiongroups
1231+
# to avoid wrapping pre-started() exceptions in an extra ExceptionGroup.
1232+
# See #2611.
1233+
try:
1234+
# set strict_exception_groups = True to make sure we always unwrap
1235+
# *this* nursery's exceptiongroup
1236+
async with open_nursery(strict_exception_groups=True) as old_nursery:
1237+
task_status: _TaskStatus[Any] = _TaskStatus(old_nursery, self)
1238+
thunk = functools.partial(async_fn, task_status=task_status)
1239+
task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
1240+
thunk, args, old_nursery, name
1241+
)
1242+
task._eventual_parent_nursery = self
1243+
# Wait for either TaskStatus.started or an exception to
1244+
# cancel this nursery:
1245+
except BaseExceptionGroup as exc:
1246+
if len(exc.exceptions) == 1:
1247+
raise exc.exceptions[0] from None
1248+
raise TrioInternalError(
1249+
"Internal nursery should not have multiple tasks. This can be "
1250+
'caused by the user managing to access the "old" nursery in '
1251+
"`task_status` and spawning tasks in it."
1252+
) from exc
1253+
12431254
# If we get here, then the child either got reparented or exited
12441255
# normally. The complicated logic is all in TaskStatus.started().
12451256
# (Any exceptions propagate directly out of the above.)

src/trio/_core/_tests/test_run.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2700,3 +2700,19 @@ async def start_raiser() -> None:
27002700
assert type(should_be_raiser_exc) == type(raiser_exc)
27012701
assert should_be_raiser_exc.message == raiser_exc.message
27022702
assert should_be_raiser_exc.exceptions == raiser_exc.exceptions
2703+
2704+
2705+
async def test_internal_error_old_nursery_multiple_tasks() -> None:
2706+
async def error_func() -> None:
2707+
raise ValueError
2708+
2709+
async def spawn_tasks_in_old_nursery(task_status: _core.TaskStatus[None]) -> None:
2710+
old_nursery = _core.current_task().parent_nursery
2711+
assert old_nursery is not None
2712+
old_nursery.start_soon(error_func)
2713+
old_nursery.start_soon(error_func)
2714+
2715+
async with _core.open_nursery() as nursery:
2716+
with pytest.raises(_core.TrioInternalError) as excinfo:
2717+
await nursery.start(spawn_tasks_in_old_nursery)
2718+
assert RaisesGroup(ValueError, ValueError).matches(excinfo.value.__cause__)

0 commit comments

Comments
 (0)