@@ -1261,6 +1261,9 @@ def _child_finished(
1261
1261
outcome : Outcome [object ],
1262
1262
) -> None :
1263
1263
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
1264
1267
if isinstance (outcome , Error ):
1265
1268
self ._add_exc (
1266
1269
outcome .error ,
@@ -2007,45 +2010,24 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
2007
2010
return task
2008
2011
2009
2012
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
+
2010
2019
# break parking lots associated with the exiting task
2011
2020
if task in GLOBAL_PARKING_LOT_BREAKER :
2012
2021
for lot in GLOBAL_PARKING_LOT_BREAKER [task ]:
2013
2022
lot .break_lot (task )
2014
2023
del GLOBAL_PARKING_LOT_BREAKER [task ]
2015
2024
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 (
2045
2026
task ._cancel_status is not None
2046
2027
and task ._cancel_status .abandoned_by_misnesting
2047
2028
and task ._cancel_status .parent is None
2048
- ):
2029
+ ) or task ._child_nurseries :
2030
+ reason = "Nursery" if task ._child_nurseries else "Cancel scope"
2049
2031
# The cancel scope surrounding this task's nursery was closed
2050
2032
# before the task exited. Force the task to exit with an error,
2051
2033
# 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:
2054
2036
# Raise this, rather than just constructing it, to get a
2055
2037
# traceback frame included
2056
2038
raise RuntimeError (
2057
- "Cancel scope stack corrupted: cancel scope surrounding "
2039
+ f" { reason } stack corrupted: { reason } surrounding "
2058
2040
f"{ task !r} was closed before the task exited\n { MISNESTING_ADVICE } " ,
2059
2041
)
2060
2042
except RuntimeError as new_exc :
0 commit comments