@@ -44,13 +44,15 @@ def _strict_shutdown_enabled() -> bool:
4444class ShutdownSummary :
4545 cancelled_tasks : int = 0
4646 executor_active : int = 0
47+ pending_tasks : int = 0
4748 suppressed_errors : int = 0
4849 forced_interrupt : bool = False
4950
5051 @property
5152 def unclean (self ) -> bool :
5253 return bool (
5354 self .executor_active
55+ or self .pending_tasks
5456 or self .suppressed_errors
5557 or self .forced_interrupt
5658 )
@@ -613,36 +615,45 @@ def _loop_exception_handler(
613615 if not strict_shutdown :
614616 shutdown_suppress .set ()
615617 # Cancel and await remaining tasks before stopping the loop.
616- async def _cancel_remaining () -> int :
618+ async def _cancel_remaining (timeout : float = 1.0 ) -> tuple [ int , int ] :
617619 tasks = [
618620 t
619621 for t in asyncio .all_tasks ()
620622 if t is not asyncio .current_task () and not t .done ()
621623 ]
622624 for t in tasks :
623625 t .cancel ()
624- if tasks :
625- await asyncio .wait (tasks )
626- return len (tasks )
626+ if not tasks :
627+ return 0 , 0
628+ _ , pending = await asyncio .wait (tasks , timeout = timeout )
629+ return len (tasks ), len (pending )
627630
628631 cancelled_count = 0
632+ pending_count = 0
629633 forced_interrupt = False
630634 fut = asyncio .run_coroutine_threadsafe (_cancel_remaining (), loop )
631635 try :
632- cancelled_count = fut .result ()
636+ cancelled_count , pending_count = fut .result ()
633637 except KeyboardInterrupt :
634638 forced_interrupt = True
635639 fut .cancel ()
636640 except Exception :
637641 cancelled_count = 0
642+ pending_count = 0
638643
639644 suppressed_count = suppressed_shutdown_errors ["count" ]
640- if cancelled_count or suppressed_count or forced_interrupt :
645+ if cancelled_count or suppressed_count or forced_interrupt or pending_count :
641646 if forced_interrupt and not cancelled_count and not suppressed_count :
642647 logger .warning (
643648 "Shutdown interrupted; tasks may still be running. "
644649 "Re-run with EZMSG_STRICT_SHUTDOWN=1 to debug tasks with poor shutdown behavior."
645650 )
651+ elif pending_count :
652+ logger .warning (
653+ "Shutdown timed out waiting for %d task(s). "
654+ "Re-run with EZMSG_STRICT_SHUTDOWN=1 to debug tasks with poor shutdown behavior." ,
655+ pending_count ,
656+ )
646657 else :
647658 logger .warning (
648659 "Shutdown suppressed %d error(s) and cancelled %d task(s). "
@@ -654,6 +665,7 @@ async def _cancel_remaining() -> int:
654665
655666 if shutdown_summary is not None :
656667 shutdown_summary .cancelled_tasks = cancelled_count
668+ shutdown_summary .pending_tasks = pending_count
657669 shutdown_summary .executor_active = (
658670 executor .active_count () if executor is not None else 0
659671 )
0 commit comments