@@ -690,10 +690,13 @@ async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
690
690
# The shield here ensures that if the context surrounding
691
691
# the loop is cancelled, we keep processing callbacks
692
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.
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().)
697
700
with trio .CancelScope (shield = True ):
698
701
while not self ._stopped .is_set ():
699
702
await self ._main_loop_one ()
@@ -754,12 +757,19 @@ async def _main_loop_exit(self):
754
757
return
755
758
756
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.
757
764
self .stop ()
758
765
await self .wait_stopped ()
759
766
760
767
# 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.
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.
763
773
while True :
764
774
try :
765
775
await self ._main_loop_one (no_wait = True )
0 commit comments