@@ -687,8 +687,16 @@ async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
687
687
sniffio .current_async_library_cvar .set ("asyncio" )
688
688
689
689
try :
690
- while not self ._stopped .is_set ():
691
- await self ._main_loop_one ()
690
+ # The shield here ensures that if the context surrounding
691
+ # the loop is cancelled, we keep processing callbacks
692
+ # until we reach the callback inserted by stop().
693
+ # There's a call to stop() in the finally block of
694
+ # open_loop(), and we're not shielding the body of the
695
+ # open_loop() context, so this should be safe against
696
+ # deadlocks.
697
+ with trio .CancelScope (shield = True ):
698
+ while not self ._stopped .is_set ():
699
+ await self ._main_loop_one ()
692
700
except StopAsyncIteration :
693
701
# raised by .stop_me() to interrupt the loop
694
702
pass
@@ -745,16 +753,20 @@ async def _main_loop_exit(self):
745
753
if self ._closed :
746
754
return
747
755
748
- self .stop ()
749
- await self .wait_stopped ()
750
-
751
- while True :
752
- try :
753
- await self ._main_loop_one (no_wait = True )
754
- except trio .WouldBlock :
755
- break
756
- except StopAsyncIteration :
757
- pass
756
+ with trio .CancelScope (shield = True ):
757
+ self .stop ()
758
+ await self .wait_stopped ()
759
+
760
+ # Drain all remaining callbacks, even those after an initial
761
+ # call to stop(). This avoids a deadlock if stop() was called
762
+ # again during unwinding.
763
+ while True :
764
+ try :
765
+ await self ._main_loop_one (no_wait = True )
766
+ except trio .WouldBlock :
767
+ break
768
+ except StopAsyncIteration :
769
+ pass
758
770
759
771
# Kill off unprocessed work
760
772
self ._cancel_fds ()
0 commit comments