@@ -687,8 +687,19 @@ 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+ # That's important to maintain the asyncio invariant
694+ # that everything you schedule before stop() will run
695+ # before the loop stops. In order to be safe against
696+ # deadlocks, it's important that the surrounding
697+ # context ensure that stop() gets called upon a
698+ # cancellation. (open_loop() does this indirectly
699+ # by calling _main_loop_exit().)
700+ with trio .CancelScope (shield = True ):
701+ while not self ._stopped .is_set ():
702+ await self ._main_loop_one ()
692703 except StopAsyncIteration :
693704 # raised by .stop_me() to interrupt the loop
694705 pass
@@ -745,16 +756,27 @@ async def _main_loop_exit(self):
745756 if self ._closed :
746757 return
747758
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
759+ with trio .CancelScope (shield = True ):
760+ # wait_stopped() will return once _main_loop() exits.
761+ # stop() inserts a callback that will cause such, and
762+ # _main_loop() doesn't block except to wait for new
763+ # callbacks to be added, so this should be deadlock-proof.
764+ self .stop ()
765+ await self .wait_stopped ()
766+
767+ # Drain all remaining callbacks, even those after an initial
768+ # call to stop(). This avoids deadlocks in some cases if
769+ # work is submitted to the loop after the shutdown process
770+ # starts. TODO: figure out precisely what this helps with,
771+ # maybe find a better way. test_wrong_context_manager_order
772+ # deadlocks if we remove it for now.
773+ while True :
774+ try :
775+ await self ._main_loop_one (no_wait = True )
776+ except trio .WouldBlock :
777+ break
778+ except StopAsyncIteration :
779+ pass
758780
759781 # Kill off unprocessed work
760782 self ._cancel_fds ()
0 commit comments