@@ -634,10 +634,13 @@ async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
634634 # The shield here ensures that if the context surrounding
635635 # the loop is cancelled, we keep processing callbacks
636636 # 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.
637+ # That's important to maintain the asyncio invariant
638+ # that everything you schedule before stop() will run
639+ # before the loop stops. In order to be safe against
640+ # deadlocks, it's important that the surrounding
641+ # context ensure that stop() gets called upon a
642+ # cancellation. (open_loop() does this indirectly
643+ # by calling _main_loop_exit().)
641644 with trio .CancelScope (shield = True ):
642645 while not self ._stopped .is_set ():
643646 await self ._main_loop_one ()
@@ -702,12 +705,19 @@ async def _main_loop_exit(self):
702705 return
703706
704707 with trio .CancelScope (shield = True ):
708+ # wait_stopped() will return once _main_loop() exits.
709+ # stop() inserts a callback that will cause such, and
710+ # _main_loop() doesn't block except to wait for new
711+ # callbacks to be added, so this should be deadlock-proof.
705712 self .stop ()
706713 await self .wait_stopped ()
707714
708715 # 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.
716+ # call to stop(). This avoids deadlocks in some cases if
717+ # work is submitted to the loop after the shutdown process
718+ # starts. TODO: figure out precisely what this helps with,
719+ # maybe find a better way. test_wrong_context_manager_order
720+ # deadlocks if we remove it for now.
711721 while True :
712722 try :
713723 await self ._main_loop_one (no_wait = True )
0 commit comments