@@ -631,8 +631,16 @@ async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
631
631
sniffio .current_async_library_cvar .set ("asyncio" )
632
632
633
633
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 ()
636
644
except StopAsyncIteration :
637
645
# raised by .stop_me() to interrupt the loop
638
646
pass
@@ -693,16 +701,20 @@ async def _main_loop_exit(self):
693
701
if self ._closed :
694
702
return
695
703
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
706
718
707
719
# Kill off unprocessed work
708
720
self ._cancel_fds ()
0 commit comments