fix(runtime): make RunManager.cancel() idempotent for already-interrupted runs (#3055) (#3058)

A second cancel() call on an interrupted run returned False, causing the
cancel and stream_existing_run router endpoints to raise 409 on double-stop.

Fix: return True inside the lock when record.status == RunStatus.interrupted.
This covers both the POST /cancel and POST /join endpoints without any
re-fetch or extra get() call — the idempotency lives at the source.

Also fixes stream_existing_run (the LangGraph SDK stop-button path), which
had the identical cancel() → 409 pattern and was not covered by the
original PR.  Both endpoints share the fix automatically.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
john lee
2026-05-20 16:37:36 +08:00
committed by GitHub
parent 6b922e4908
commit 9b19cca91c
2 changed files with 148 additions and 1 deletions
@@ -258,12 +258,17 @@ class RunManager:
action: "interrupt" keeps checkpoint, "rollback" reverts to pre-run state.
Sets the abort event with the action reason and cancels the asyncio task.
Returns ``True`` if the run was in-flight and cancellation was initiated.
Returns ``True`` if cancellation was initiated **or** the run was already
interrupted (idempotent — a second cancel is a no-op success).
Returns ``False`` only when the run is unknown to this worker or has
reached a terminal state other than interrupted (completed, failed, etc.).
"""
async with self._lock:
record = self._runs.get(run_id)
if record is None:
return False
if record.status == RunStatus.interrupted:
return True # idempotent — already cancelled on this worker
if record.status not in (RunStatus.pending, RunStatus.running):
return False
record.abort_action = action