@@ -687,8 +687,16 @@ async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
687687 sniffio .current_async_library_cvar .set ("asyncio" )
688688
689689 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 ()
692700 except StopAsyncIteration :
693701 # raised by .stop_me() to interrupt the loop
694702 pass
@@ -745,16 +753,20 @@ async def _main_loop_exit(self):
745753 if self ._closed :
746754 return
747755
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
758770
759771 # Kill off unprocessed work
760772 self ._cancel_fds ()
0 commit comments