@@ -687,8 +687,19 @@ async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
687
687
sniffio .current_async_library_cvar .set ("asyncio" )
688
688
689
689
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 ()
692
703
except StopAsyncIteration :
693
704
# raised by .stop_me() to interrupt the loop
694
705
pass
@@ -745,16 +756,27 @@ async def _main_loop_exit(self):
745
756
if self ._closed :
746
757
return
747
758
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
758
780
759
781
# Kill off unprocessed work
760
782
self ._cancel_fds ()
0 commit comments