Add jobs-namespace cancel endpoints (POST /api/jobs/{job_id}/cancel, … · code/app-python-comfyui@4e716f7 · GitHub
Skip to content

Commit 4e716f7

Browse files
authored
Add jobs-namespace cancel endpoints (POST /api/jobs/{job_id}/cancel, POST /api/jobs/cancel) (Comfy-Org#14493)
* Add jobs-namespace cancel endpoints Add two cancel endpoints under the jobs namespace so a job can be cancelled by id without the caller needing to know whether the job is running or pending, or branching between /interrupt and /queue. - POST /api/jobs/{job_id}/cancel cancels one job by id. Idempotent: an already-finished or unknown id returns 200 {"cancelled": false} rather than an error. - POST /api/jobs/cancel takes {"job_ids": [...]} and cancels a batch. Fail-fast: if any id is unknown the request returns 404 listing the unknown ids and cancels nothing (no partial side effects). Both are state-agnostic and map onto the existing queue mechanics: a running job is interrupted (same path as /interrupt), a pending job is dequeued (same path as /queue {"delete": [...]}). The cancel logic lives in comfy_execution.jobs as pure, unit-tested helpers; the server handlers are thin wrappers. openapi.yaml documents both routes. * fix: resolve review feedback on cancel endpoints - Guard cancel_job() against TOCTOU: when dequeue() returns False the pending job left the queue between snapshot and delete; return CANCEL_UNKNOWN so callers never report cancelled=True for a remove that did not happen. - Validate each job_ids element in the batch cancel endpoint before any queue access; unhashable or non-UUID values now return 400 instead of raising TypeError (500). - Update batch HTTP tests to use canonical UUID ids (required now that the endpoint validates id format) and add tests for the new guards. * fix: make job cancel atomic and best-effort Addresses two cancel races/edges raised in review. Targeted, atomic interrupt. cancel_job's interrupt callback now takes the prompt id and returns whether it fired; the single-cancel route backs it with the new PromptQueue.interrupt_if_running, which checks the running set and signals the interrupt under the queue mutex. This closes the TOCTOU where a pending job that starts executing between the snapshot and dequeue (or a running job that finishes between the snapshot and interrupt) could be missed or, worse, cause an unrelated prompt to be interrupted. The per-prompt interrupt-flag reset in execute_async keeps a finished job from leaking the interrupt onto its successor. Best-effort batch cancel. POST /api/jobs/cancel no longer fails the whole batch with 404 when one id is unknown/finished; such ids are treated as no-ops, so "cancel all" still cancels the in-progress jobs even if some finished between the client's snapshot and the request. Malformed ids are still rejected with 400.
1 parent 2ab3816 commit 4e716f7

5 files changed

Lines changed: 662 additions & 2 deletions

File tree

‎comfy_execution/jobs.py‎

Lines changed: 80 additions & 1 deletion

‎execution.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,25 @@ def get_current_queue_volatile(self):
13081308
queued = copy.copy(self.queue)
13091309
return (running, queued)
13101310

1311+
def interrupt_if_running(self, prompt_id):
1312+
"""Interrupt the running prompt with this id, atomically.
1313+
1314+
Checks the live running set and signals the interrupt under the queue
1315+
mutex, so the worker cannot move the job to done (and start the next
1316+
prompt) in between. Returns True if a matching job was running and an
1317+
interrupt was signalled, False otherwise. The atomicity is what keeps a
1318+
cancel from landing on an unrelated prompt that started after a separate
1319+
is-running check: the global interrupt flag is reset at the start of
1320+
every prompt (execute_async), so a job that finishes before consuming
1321+
the flag cannot leak the interrupt onto its successor.
1322+
"""
1323+
with self.mutex:
1324+
for item in self.currently_running.values():
1325+
if item[1] == prompt_id:
1326+
nodes.interrupt_processing()
1327+
return True
1328+
return False
1329+
13111330
def get_tasks_remaining(self):
13121331
with self.mutex:
13131332
return len(self.queue) + len(self.currently_running)

‎server.py‎

Lines changed: 110 additions & 1 deletion

‎tests-unit/jobs_cancel_test/__init__.py‎

Whitespace-only changes.

0 commit comments

Comments
 (0)