fix(kanban): archive_task now kills the worker process

archive_task() was a pure DB operation — it cleared worker_pid,
claim_lock, and status from the tasks row but never sent SIGTERM
to the actual OS process. A running worker stayed alive until it
next called kanban_complete/kanban_block and discovered it was
archived, burning API quota and compute resources.

Fix: snapshot pid+claim_lock before the write_txn clears them,
then call _terminate_reclaimed_worker() — same function reclaim_task
uses — which sends SIGTERM, waits 5s, then SIGKILL if still alive.
The termination metadata is included in the 'archived' event so
operators can see what happened.

Order matches reclaim_task: terminate first, then DB update.
For non-running / non-local tasks, _terminate_reclaimed_worker
returns immediately as a no-op.

Closes #33774 reprise: the scratch-workspace side was fixed in
fc8afd500, but the orphaned-process side was never addressed.
This commit is contained in:
PINKIIILQWQ
2026-06-09 21:13:51 +08:00
committed by Teknium
parent 47c029927f
commit 907f409dc4
+15 -1
View File
@@ -3495,6 +3495,18 @@ def specify_triage_task(
def archive_task(conn: sqlite3.Connection, task_id: str) -> bool:
# Snapshot pid+claim before the DB update clears them, so we can
# terminate the worker process even after those columns are NULLed.
row = conn.execute(
"SELECT worker_pid, claim_lock FROM tasks WHERE id = ?",
(task_id,),
).fetchone()
prev_pid = row["worker_pid"] if row else None
prev_lock = row["claim_lock"] if row else None
# Terminate the worker process first (same order as reclaim_task).
# If the task wasn't running on this host, this is a safe no-op.
termination = _terminate_reclaimed_worker(prev_pid, prev_lock)
with write_txn(conn):
cur = conn.execute(
"UPDATE tasks SET status = 'archived', "
@@ -3508,7 +3520,9 @@ def archive_task(conn: sqlite3.Connection, task_id: str) -> bool:
conn, task_id, outcome="reclaimed", status="reclaimed",
summary="task archived with run still active",
)
_append_event(conn, task_id, "archived", None, run_id=run_id)
payload = {}
payload.update(termination)
_append_event(conn, task_id, "archived", payload, run_id=run_id)
# ``archived`` parents no longer block children; promote them now.
recompute_ready(conn)
# Reap the workspace on archive too (never-completed tasks kept it forever).