@@ -631,8 +631,16 @@ async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
631631 sniffio .current_async_library_cvar .set ("asyncio" )
632632
633633 try :
634- while not self ._stopped .is_set ():
635- await self ._main_loop_one ()
634+ # The shield here ensures that if the context surrounding
635+ # the loop is cancelled, we keep processing callbacks
636+ # until we reach the callback inserted by stop().
637+ # There's a call to stop() in the finally block of
638+ # open_loop(), and we're not shielding the body of the
639+ # open_loop() context, so this should be safe against
640+ # deadlocks.
641+ with trio .CancelScope (shield = True ):
642+ while not self ._stopped .is_set ():
643+ await self ._main_loop_one ()
636644 except StopAsyncIteration :
637645 # raised by .stop_me() to interrupt the loop
638646 pass
@@ -693,16 +701,20 @@ async def _main_loop_exit(self):
693701 if self ._closed :
694702 return
695703
696- self .stop ()
697- await self .wait_stopped ()
698-
699- while True :
700- try :
701- await self ._main_loop_one (no_wait = True )
702- except trio .WouldBlock :
703- break
704- except StopAsyncIteration :
705- pass
704+ with trio .CancelScope (shield = True ):
705+ self .stop ()
706+ await self .wait_stopped ()
707+
708+ # Drain all remaining callbacks, even those after an initial
709+ # call to stop(). This avoids a deadlock if stop() was called
710+ # again during unwinding.
711+ while True :
712+ try :
713+ await self ._main_loop_one (no_wait = True )
714+ except trio .WouldBlock :
715+ break
716+ except StopAsyncIteration :
717+ pass
706718
707719 # Kill off unprocessed work
708720 self ._cancel_fds ()
0 commit comments