Sofortiger Cancel: Laufende Claude-Prozesse per Event abbrechen
Bisher war Cancel kooperativ (Flag-basiert) -- der Code pruefte das Flag nur an wenigen Checkpoints. Laufende Claude CLI Subprozesse (WebSearch, Analyse, Faktencheck) liefen bis zum Ende weiter, was minutenlanges Warten beim Abbrechen verursachte. Neuer Ansatz: - ContextVar _cancel_event_var in claude_client.py - Orchestrator setzt asyncio.Event vor jedem Refresh - call_claude wartet parallel auf Prozess UND cancel_event - Bei Cancel: process.kill() + CancelledError sofort - Kein Durchreichen durch Agent-Methoden noetig (contextvars)
Dieser Commit ist enthalten in:
@@ -1,10 +1,15 @@
|
||||
"""Shared Claude CLI Client mit Usage-Tracking."""
|
||||
import asyncio
|
||||
import contextvars
|
||||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from config import CLAUDE_PATH, CLAUDE_TIMEOUT, CLAUDE_MODEL_FAST
|
||||
|
||||
# ContextVar fuer Cancel-Event: Wird vom Orchestrator gesetzt,
|
||||
# call_claude prueft automatisch darauf -- kein Durchreichen noetig.
|
||||
_cancel_event_var: contextvars.ContextVar[asyncio.Event | None] = contextvars.ContextVar("_cancel_event_var", default=None)
|
||||
|
||||
logger = logging.getLogger("osint.claude_client")
|
||||
|
||||
|
||||
@@ -78,9 +83,37 @@ async def call_claude(prompt: str, tools: str | None = "WebSearch,WebFetch", mod
|
||||
},
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
process.communicate(input=prompt.encode("utf-8")), timeout=CLAUDE_TIMEOUT
|
||||
)
|
||||
cancel_event = _cancel_event_var.get(None)
|
||||
if cancel_event:
|
||||
# Cancel-aware: Monitor cancel_event while process runs
|
||||
communicate_task = asyncio.create_task(
|
||||
process.communicate(input=prompt.encode("utf-8"))
|
||||
)
|
||||
cancel_wait_task = asyncio.create_task(cancel_event.wait())
|
||||
timeout_task = asyncio.create_task(asyncio.sleep(CLAUDE_TIMEOUT))
|
||||
|
||||
done, pending = await asyncio.wait(
|
||||
[communicate_task, cancel_wait_task, timeout_task],
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
|
||||
for p in pending:
|
||||
p.cancel()
|
||||
|
||||
if communicate_task in done:
|
||||
stdout, stderr = communicate_task.result()
|
||||
elif cancel_wait_task in done:
|
||||
process.kill()
|
||||
await process.wait()
|
||||
raise asyncio.CancelledError("Cancel angefordert")
|
||||
else:
|
||||
process.kill()
|
||||
await process.wait()
|
||||
raise TimeoutError(f"Claude CLI Timeout nach {CLAUDE_TIMEOUT}s")
|
||||
else:
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
process.communicate(input=prompt.encode("utf-8")), timeout=CLAUDE_TIMEOUT
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
process.kill()
|
||||
raise TimeoutError(f"Claude CLI Timeout nach {CLAUDE_TIMEOUT}s")
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren