@@ -1754,6 +1754,56 @@ def stop_gracefully(self) -> None:
17541754 f"(event-loop not yet started)."
17551755 )
17561756
1757+ async def _async_stop_immediately (self ) -> None :
1758+ r"""Force-stop the workforce without waiting for in-flight tasks."""
1759+ self ._stop_requested = True
1760+ self ._pause_event .set ()
1761+ logger .warning (f"Workforce { self .node_id } force stop requested." )
1762+
1763+ # Remove pending tasks and clear channel postings to avoid new work
1764+ self ._pending_tasks .clear ()
1765+ if self ._channel :
1766+ try :
1767+ in_flight = await self ._channel .get_in_flight_tasks (
1768+ self .node_id
1769+ )
1770+ for task in in_flight :
1771+ await self ._channel .remove_task (task .id )
1772+ except Exception as e :
1773+ logger .error (
1774+ f"Failed to clear in-flight tasks during force stop: { e } " ,
1775+ exc_info = True ,
1776+ )
1777+ self ._in_flight_tasks = 0
1778+
1779+ # Stop child nodes and cancel their listening tasks immediately
1780+ for child in self ._children :
1781+ if child ._running :
1782+ child .stop ()
1783+ for task in self ._child_listening_tasks :
1784+ if not task .done ():
1785+ task .cancel ()
1786+
1787+ self ._running = False
1788+ self ._state = WorkforceState .STOPPED
1789+
1790+ def stop_immediately (self ) -> None :
1791+ r"""Force-stop without waiting for current tasks to finish."""
1792+ if self ._loop and not self ._loop .is_closed ():
1793+ self ._submit_coro_to_loop (self ._async_stop_immediately ())
1794+ else :
1795+ # No running loop; best-effort synchronous cleanup
1796+ self ._stop_requested = True
1797+ self ._pause_event .set ()
1798+ self ._pending_tasks .clear ()
1799+ self ._in_flight_tasks = 0
1800+ self ._running = False
1801+ self ._state = WorkforceState .STOPPED
1802+ logger .warning (
1803+ f"Workforce { self .node_id } force stopped "
1804+ f"(event-loop not yet started)."
1805+ )
1806+
17571807 async def _async_skip_gracefully (self ) -> None :
17581808 r"""Async implementation of skip_gracefully to run on the event
17591809 loop.
@@ -4990,54 +5040,34 @@ async def start(self) -> None:
49905040
49915041 @check_if_running (True )
49925042 def stop (self ) -> None :
4993- r"""Stop all the child nodes under it. The node itself will be stopped
4994- by its parent node.
4995- """
4996- # Stop all child nodes first
4997- for child in self ._children :
4998- if child ._running :
4999- child .stop ()
5043+ r"""Forcefully stop the workforce and its children immediately.
50005044
5001- # Cancel child listening tasks
5002- if self ._child_listening_tasks :
5003- try :
5004- loop = asyncio .get_running_loop ()
5005- if loop and not loop .is_closed ():
5006- # Create graceful cleanup task
5007- async def cleanup ():
5008- await asyncio .sleep (0.1 ) # Brief grace period
5009- for task in self ._child_listening_tasks :
5010- if not task .done ():
5011- task .cancel ()
5012-
5013- # Handle both asyncio.Task and concurrent.futures.
5014- # Future
5015- awaitables = []
5016- for task in self ._child_listening_tasks :
5017- if isinstance (task , concurrent .futures .Future ):
5018- # Convert Future to awaitable
5019- awaitables .append (asyncio .wrap_future (task ))
5020- else :
5021- # Already an asyncio.Task
5022- awaitables .append (task )
5023-
5024- await asyncio .gather (
5025- * awaitables ,
5026- return_exceptions = True ,
5027- )
5028-
5029- self ._cleanup_task = loop .create_task (cleanup ())
5030- else :
5031- # No active loop, cancel immediately
5032- for task in self ._child_listening_tasks :
5033- task .cancel ()
5034- except (RuntimeError , Exception ) as e :
5035- # Fallback: cancel immediately
5036- logger .debug (f"Exception during task cleanup: { e } " )
5037- for task in self ._child_listening_tasks :
5045+ This is now an immediate stop (was previously a graceful lifecycle
5046+ cleanup). It cancels child listeners, clears pending/in-flight tasks,
5047+ and sets state to STOPPED without waiting for active work to finish.
5048+ """
5049+ if self ._loop and not self ._loop .is_closed ():
5050+ self ._submit_coro_to_loop (self ._async_stop_immediately ())
5051+ else :
5052+ # No running loop; perform synchronous best-effort force stop
5053+ self ._stop_requested = True
5054+ self ._pause_event .set ()
5055+ self ._pending_tasks .clear ()
5056+ self ._in_flight_tasks = 0
5057+ # Stop children
5058+ for child in self ._children :
5059+ if child ._running :
5060+ child .stop ()
5061+ # Cancel listening tasks
5062+ for task in self ._child_listening_tasks :
5063+ if not task .done ():
50385064 task .cancel ()
5039-
5040- self ._running = False
5065+ self ._running = False
5066+ self ._state = WorkforceState .STOPPED
5067+ logger .warning (
5068+ f"Workforce { self .node_id } force stopped "
5069+ f"(event-loop not yet started)."
5070+ )
50415071
50425072 def clone (self , with_memory : bool = False ) -> 'Workforce' :
50435073 r"""Creates a new instance of Workforce with the same configuration.
0 commit comments