diff --git a/backend/agents.py b/backend/agents.py index 139fce0..9db9f97 100644 --- a/backend/agents.py +++ b/backend/agents.py @@ -9,6 +9,7 @@ import logging import os import re import shutil +import signal import tempfile import time import urllib.request @@ -84,6 +85,18 @@ def provider_available(provider: str) -> bool: return True +def _kill(process) -> None: + """Killt den Agenten samt Kindprozessen über die Prozess-Gruppe (sonst überleben die + von der CLI gestarteten Kinder, halten die Pipes offen und blockieren communicate()).""" + try: + os.killpg(os.getpgid(process.pid), signal.SIGKILL) + except (ProcessLookupError, PermissionError): + try: + process.kill() + except ProcessLookupError: + pass + + def kill_process(agent_key_prefix: str) -> None: """Killt alle aktiven Prozesse, deren Key mit dem Prefix beginnt (deckt -plan/-w1… ab).""" for key, process in list(_active_processes.items()): @@ -92,7 +105,7 @@ def kill_process(agent_key_prefix: str) -> None: continue if key.startswith(agent_key_prefix): log.debug("kill agent %s", key) - process.kill() + _kill(process) async def run_agent( @@ -128,6 +141,7 @@ async def _communicate(agent_key: str, cmd: list[str], stdin_data: bytes | None, stdin=asyncio.subprocess.PIPE if stdin_data is not None else asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + start_new_session=True, # eigene Prozess-Gruppe → killpg killt auch Kindprozesse ) if stagger: @@ -144,7 +158,7 @@ async def _communicate(agent_key: str, cmd: list[str], stdin_data: bytes | None, timeout=timeout, ) except asyncio.TimeoutError: - process.kill() + _kill(process) try: await asyncio.wait_for(process.wait(), timeout=5) except asyncio.TimeoutError: