8
8
import time
9
9
import types
10
10
import weakref
11
- from contextlib import ExitStack , contextmanager , suppress
11
+ from contextlib import (
12
+ AsyncExitStack ,
13
+ ExitStack ,
14
+ asynccontextmanager ,
15
+ contextmanager ,
16
+ suppress ,
17
+ )
12
18
from math import inf , nan
13
19
from typing import TYPE_CHECKING , NoReturn , TypeVar
14
20
from unittest import mock
@@ -761,7 +767,7 @@ async def enter_scope() -> None:
761
767
assert scope .cancel_called # never become un-cancelled
762
768
763
769
764
- async def test_cancel_scope_misnesting () -> None :
770
+ async def test_cancel_scope_misnesting_1 () -> None :
765
771
outer = _core .CancelScope ()
766
772
inner = _core .CancelScope ()
767
773
with ExitStack () as stack :
@@ -771,6 +777,8 @@ async def test_cancel_scope_misnesting() -> None:
771
777
stack .close ()
772
778
# No further error is raised when exiting the inner context
773
779
780
+
781
+ async def test_cancel_scope_misnesting_2 () -> None :
774
782
# If there are other tasks inside the abandoned part of the cancel tree,
775
783
# they get cancelled when the misnesting is detected
776
784
async def task1 () -> None :
@@ -828,6 +836,8 @@ def no_context(exc: RuntimeError) -> bool:
828
836
)
829
837
assert group .matches (exc_info .value .__context__ )
830
838
839
+
840
+ async def test_cancel_scope_misnesting_3 () -> None :
831
841
# Trying to exit a cancel scope from an unrelated task raises an error
832
842
# without affecting any state
833
843
async def task3 (task_status : _core .TaskStatus [_core .CancelScope ]) -> None :
@@ -844,6 +854,42 @@ async def task3(task_status: _core.TaskStatus[_core.CancelScope]) -> None:
844
854
scope .cancel ()
845
855
846
856
857
+ async def test_nursery_misnest () -> None :
858
+ # See https://github.com/python-trio/trio/issues/3298
859
+ async def inner_func () -> None :
860
+ inner_nursery = await inner_cm .__aenter__ ()
861
+ inner_nursery .start_soon (sleep , 1 )
862
+
863
+ with pytest .RaisesGroup (
864
+ pytest .RaisesExc (RuntimeError , match = "Cancel scope stack corrupted" )
865
+ ):
866
+ async with _core .open_nursery () as outer_nursery :
867
+ inner_cm = _core .open_nursery ()
868
+ outer_nursery .start_soon (inner_func )
869
+
870
+
871
+ async def test_asyncexitstack_nursery_misnest () -> None :
872
+ @asynccontextmanager
873
+ async def asynccontextmanager_that_creates_a_nursery_internally () -> (
874
+ AsyncGenerator [None ]
875
+ ):
876
+ async with _core .open_nursery () as nursery :
877
+ nursery .start_soon (
878
+ print_sleep_print , "task_in_asynccontextmanager_nursery" , 2.0
879
+ )
880
+ yield
881
+
882
+ async def print_sleep_print (name : str , sleep_time : float ) -> None :
883
+ await sleep (sleep_time )
884
+
885
+ async with AsyncExitStack () as stack , _core .open_nursery () as nursery :
886
+ # The asynccontextmanager is going to create a nursery that outlives this nursery!
887
+ nursery .start_soon (
888
+ stack .enter_async_context ,
889
+ asynccontextmanager_that_creates_a_nursery_internally (),
890
+ )
891
+
892
+
847
893
@slow
848
894
async def test_timekeeping () -> None :
849
895
# probably a good idea to use a real clock for *one* test anyway...
0 commit comments