Skip to content

Commit 06640eb

Browse files
committed
new approach
1 parent b33194d commit 06640eb

File tree

2 files changed

+27
-49
lines changed

2 files changed

+27
-49
lines changed

src/trio/_core/_run.py

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,9 @@ def _child_finished(
12611261
outcome: Outcome[object],
12621262
) -> None:
12631263
self._children.remove(task)
1264+
if self._closed and not hasattr(self, "_pending_excs"):
1265+
# We're abandoned by misnested nurseries, the result of the task is lost.
1266+
return
12641267
if isinstance(outcome, Error):
12651268
self._add_exc(
12661269
outcome.error,
@@ -2007,45 +2010,24 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
20072010
return task
20082011

20092012
def task_exited(self, task: Task, outcome: Outcome[object]) -> None:
2013+
if task._child_nurseries:
2014+
for nursery in task._child_nurseries:
2015+
nursery.cancel_scope.cancel() # TODO: add reason
2016+
nursery._parent_waiting_in_aexit = False
2017+
nursery._closed = True
2018+
20102019
# break parking lots associated with the exiting task
20112020
if task in GLOBAL_PARKING_LOT_BREAKER:
20122021
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
20132022
lot.break_lot(task)
20142023
del GLOBAL_PARKING_LOT_BREAKER[task]
20152024

2016-
if task._child_nurseries:
2017-
# Forcefully abort any tasks spawned by the misnested nursery to
2018-
# avoid internal errors.
2019-
runner = GLOBAL_RUN_CONTEXT.runner
2020-
for nursery in task._child_nurseries:
2021-
for child in nursery._children.copy():
2022-
if child in runner.runq:
2023-
runner.runq.remove(child)
2024-
self.task_exited(
2025-
child,
2026-
Error(
2027-
RuntimeError(
2028-
f"Task {child} aborted after nursery was destroyed due to misnesting."
2029-
)
2030-
),
2031-
)
2032-
assert not nursery._children
2033-
try:
2034-
# Raise this, rather than just constructing it, to get a
2035-
# traceback frame included
2036-
raise RuntimeError(
2037-
"Nursery stack corrupted: nurseries spawned by "
2038-
f"{task!r} was still live when the task exited\n{MISNESTING_ADVICE}",
2039-
)
2040-
except RuntimeError as new_exc:
2041-
if isinstance(outcome, Error):
2042-
new_exc.__context__ = outcome.error
2043-
outcome = Error(new_exc)
2044-
elif (
2025+
if (
20452026
task._cancel_status is not None
20462027
and task._cancel_status.abandoned_by_misnesting
20472028
and task._cancel_status.parent is None
2048-
):
2029+
) or task._child_nurseries:
2030+
reason = "Nursery" if task._child_nurseries else "Cancel scope"
20492031
# The cancel scope surrounding this task's nursery was closed
20502032
# before the task exited. Force the task to exit with an error,
20512033
# since the error might not have been caught elsewhere. See the
@@ -2054,7 +2036,7 @@ def task_exited(self, task: Task, outcome: Outcome[object]) -> None:
20542036
# Raise this, rather than just constructing it, to get a
20552037
# traceback frame included
20562038
raise RuntimeError(
2057-
"Cancel scope stack corrupted: cancel scope surrounding "
2039+
f"{reason} stack corrupted: {reason} surrounding "
20582040
f"{task!r} was closed before the task exited\n{MISNESTING_ADVICE}",
20592041
)
20602042
except RuntimeError as new_exc:

src/trio/_core/_tests/test_run.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,17 @@ async def inner_func() -> None:
868868
outer_nursery.start_soon(inner_func)
869869

870870

871+
def test_nursery_nested_child_misnest() -> None:
872+
# TODO: check context as well for the AssertionError (that will be a RuntimeError)
873+
async def main() -> None:
874+
async with _core.open_nursery():
875+
inner_cm = _core.open_nursery()
876+
await inner_cm.__aenter__()
877+
878+
with pytest.raises(RuntimeError, match="Nursery stack corrupted"):
879+
_core.run(main)
880+
881+
871882
async def test_asyncexitstack_nursery_misnest() -> None:
872883
@asynccontextmanager
873884
async def asynccontextmanager_that_creates_a_nursery_internally() -> (
@@ -883,17 +894,11 @@ async def started_sleeper(task_status: _core.TaskStatus[None]) -> None:
883894
await sleep_forever()
884895

885896
async def unstarted_task() -> None:
886-
raise AssertionError("this should not even get a chance to run")
897+
await _core.checkpoint()
887898

888899
with pytest.RaisesGroup(
889-
pytest.RaisesExc(
890-
RuntimeError,
891-
match="Task .*unstarted_task.* aborted after nursery was destroyed due to misnesting.",
892-
),
893-
pytest.RaisesExc(
894-
RuntimeError,
895-
match="Task .*started_sleeper.* aborted after nursery was destroyed due to misnesting.",
896-
),
900+
_core.Cancelled, # this leaks out, likely the scope supposed to handle it is gone
901+
# but one of them is handled, or lost, not sure (TODO).
897902
pytest.RaisesGroup(
898903
pytest.RaisesExc(RuntimeError, match="Nursery stack corrupted")
899904
),
@@ -905,15 +910,6 @@ async def unstarted_task() -> None:
905910
asynccontextmanager_that_creates_a_nursery_internally(),
906911
)
907912

908-
# The outer nursery forcefully aborts the inner nursery and stops `unstarted_task`
909-
# from ever being started.
910-
# `started_sleeper` is awaited, but not the internal `sleep`
911-
with pytest.warns(
912-
RuntimeWarning,
913-
match="^coroutine '(test_asyncexitstack_nursery_misnest.<locals>.unstarted_task|sleep)' was never awaited$",
914-
):
915-
gc_collect_harder()
916-
917913

918914
@slow
919915
async def test_timekeeping() -> None:

0 commit comments

Comments
 (0)