@@ -993,19 +993,26 @@ async def async_time_bomb() -> None:
993993 assert cancel_scope .cancelled_caught
994994
995995
996- async def test_from_thread_check_cancelled () -> None :
997- q : stdlib_queue .Queue [str | BaseException ] = stdlib_queue .Queue ()
996+ async def child (
997+ abandon_on_cancel : bool ,
998+ scope : CancelScope ,
999+ record : list [str ],
1000+ f : Callable [[], None ],
1001+ ) -> None :
1002+ with scope :
1003+ record .append ("start" )
1004+ try :
1005+ return await to_thread_run_sync (f , abandon_on_cancel = abandon_on_cancel )
1006+ except _core .Cancelled as e :
1007+ record .append (str (e ))
1008+ raise
1009+ finally :
1010+ record .append ("exit" )
9981011
999- async def child (abandon_on_cancel : bool , scope : CancelScope ) -> None :
1000- with scope :
1001- record .append ("start" )
1002- try :
1003- return await to_thread_run_sync (f , abandon_on_cancel = abandon_on_cancel )
1004- except _core .Cancelled as e :
1005- record .append (str (e ))
1006- raise
1007- finally :
1008- record .append ("exit" )
1012+
1013+ @pytest .mark .parametrize ("cancel_the_scope" , [False , True ])
1014+ async def test_from_thread_check_cancelled_no_abandon (cancel_the_scope : bool ) -> None :
1015+ q : stdlib_queue .Queue [str | BaseException ] = stdlib_queue .Queue ()
10091016
10101017 def f () -> None :
10111018 try :
@@ -1017,42 +1024,42 @@ def f() -> None:
10171024 ev .wait ()
10181025 return from_thread_check_cancelled ()
10191026
1020- # Base case: nothing cancelled so we shouldn't see cancels anywhere
10211027 record : list [str ] = []
10221028 ev = threading .Event ()
1023- async with _core .open_nursery () as nursery :
1024- nursery .start_soon (child , False , _core .CancelScope ())
1025- await wait_all_tasks_blocked ()
1026- assert record [0 ] == "start"
1027- assert q .get (timeout = 1 ) == "Not Cancelled"
1028- ev .set ()
1029- # implicit assertion, Cancelled not raised via nursery
1030- assert record [1 ] == "exit"
1031-
1032- # abandon_on_cancel=False case: a cancel will pop out but be handled by
1033- # the appropriate cancel scope
1034- record = []
1035- ev = threading .Event ()
10361029 scope = _core .CancelScope () # Nursery cancel scope gives false positives
1030+
10371031 async with _core .open_nursery () as nursery :
1038- nursery .start_soon (child , False , scope )
1032+ nursery .start_soon (child , False , scope , record , f )
10391033 await wait_all_tasks_blocked ()
10401034 assert record [0 ] == "start"
10411035 assert q .get (timeout = 1 ) == "Not Cancelled"
1042- scope .cancel ()
1036+ if cancel_the_scope :
1037+ scope .cancel ()
10431038 ev .set ()
1044- assert scope .cancelled_caught
1045- assert re .fullmatch (
1046- r"cancelled due to explicit from task "
1047- r"<Task 'trio._tests.test_threads.test_from_thread_check_cancelled' at 0x\w*>" ,
1048- record [1 ],
1049- ), record [1 ]
1050- assert record [2 ] == "exit"
1051- assert len (record ) == 3
1039+ # Base case: nothing cancelled so we shouldn't see cancels anywhere
1040+ if not cancel_the_scope :
1041+ # implicit assertion, Cancelled not raised via nursery
1042+ assert record [1 ] == "exit"
1043+ else :
1044+ # abandon_on_cancel=False case: a cancel will pop out but be handled by
1045+ # the appropriate cancel scope
1046+
1047+ assert scope .cancelled_caught
1048+ assert re .fullmatch (
1049+ r"cancelled due to explicit from task "
1050+ r"<Task 'trio._tests.test_threads.test_from_thread_check_cancelled_no_abandon' at 0x\w*>" ,
1051+ record [1 ],
1052+ ), record [1 ]
1053+ assert record [2 ] == "exit"
1054+ assert len (record ) == 3
1055+
10521056
1057+ async def test_from_thread_check_cancelled_abandon_on_cancel () -> None :
1058+ q : stdlib_queue .Queue [str | BaseException ] = stdlib_queue .Queue ()
10531059 # abandon_on_cancel=True case: slightly different thread behavior needed
10541060 # check thread is cancelled "soon" after abandonment
1055- def f () -> None : # type: ignore[no-redef] # noqa: F811
1061+
1062+ def f () -> None :
10561063 ev .wait ()
10571064 try :
10581065 from_thread_check_cancelled ()
@@ -1065,19 +1072,22 @@ def f() -> None: # type: ignore[no-redef] # noqa: F811
10651072 else : # pragma: no cover, test failure path
10661073 q .put ("Not Cancelled" )
10671074
1068- record = []
1075+ record : list [ str ] = []
10691076 ev = threading .Event ()
10701077 scope = _core .CancelScope ()
10711078 async with _core .open_nursery () as nursery :
1072- nursery .start_soon (child , True , scope )
1079+ nursery .start_soon (child , True , scope , record , f )
10731080 await wait_all_tasks_blocked ()
10741081 assert record [0 ] == "start"
10751082 scope .cancel ()
1076- ev .set ()
1083+ # In the worst case the nursery fully exits before the threaded function
1084+ # checks for cancellation.
1085+ ev .set ()
1086+
10771087 assert scope .cancelled_caught
10781088 assert re .fullmatch (
10791089 r"cancelled due to explicit from task "
1080- r"<Task 'trio._tests.test_threads.test_from_thread_check_cancelled ' at 0x\w*>" ,
1090+ r"<Task 'trio._tests.test_threads.test_from_thread_check_cancelled_abandon_on_cancel ' at 0x\w*>" ,
10811091 record [1 ],
10821092 ), record [1 ]
10831093 assert record [- 1 ] == "exit"
@@ -1087,7 +1097,7 @@ def f() -> None: # type: ignore[no-redef] # noqa: F811
10871097 else :
10881098 assert re .fullmatch (
10891099 r"cancelled due to explicit from task "
1090- r"<Task 'trio._tests.test_threads.test_from_thread_check_cancelled ' at 0x\w*>" ,
1100+ r"<Task 'trio._tests.test_threads.test_from_thread_check_cancelled_abandon_on_cancel ' at 0x\w*>" ,
10911101 res ,
10921102 ), res
10931103
0 commit comments