Skip to content

cancel_job doesn't take effect during a model's loading phase, and the job shows no pending cancel #892

Closed

Description

@dkackman

Filed by tester (claude-opus-5-5 via anthropic) on mps on mini-ai.lan, develop @ dfc7c49, while verifying #883.

Tool: cancel_job / wait_for_job

Repro

  1. run_workflow(name="templates/minimax/first-and-last-frame", arguments={first/last image = "asset:qa-cast/priya-portrait.jpg", "width": 512, "height": 288, "num_frames": 124, "seed": 42}, acknowledged_cost=true) started job a1264f45fe71. On this server, validate_workflow reports downloads_required: MiniMaxAI/MiniMax-H3 464.2 GB and lightx2v/Minimax-h3-Turbo 54.7 GB.
  2. The job sat in phase: loading, phase_detail: "pipeline: MiniMaxAI/MiniMax-H3" for over 13 minutes, emitting only phase_stall events.
  3. cancel_job(job_id="a1264f45fe71") → {"id": "a1264f45fe71", "status": "running"}.
  4. wait_for_job for a total of 420 s afterwards → still status: running, phase: loading, seconds_in_phase: 1237. Nothing in the reply shows a cancel was requested.

Expected: a cancel requested during loading, including a model download, takes effect within a bounded time. If loading genuinely can't be interrupted, the job reply should at least show the pending cancel (e.g. cancel_requested: true), and the cancel_job docstring should say loading isn't a boundary.

Actual: the job keeps the single GPU worker indefinitely, and the consumer can't tell its cancel was recorded.

A related question for the implementer: H3 can't be expected to load on a 64 GB unified-memory box. If validate or run could warn about that before starting a ~520 GB download, this job would never have been queued here.

Activity

  1. added
    target:mini-aiFound against a non-lem server (DW_TARGET=mini-ai); Don triages
    and removed
    target:mini-aiFound against a non-lem server (DW_TARGET=mini-ai); Don triages
    on Oct 14, 2026
  2. dkackman commented on Oct 14, 2026

    @dkackman
    OwnerAuthor

    Moved to lem's loop (Don's call): the repro runs templates/minimax/first-and-last-frame, and H3 can't load on mini-ai, so the load-phase cancel has to be reproduced and verified on lem, where H3 is cached and loads. Claim swapped target:mini-ai → target:lem; backend:shared kept, since cancel-during-load isn't CUDA code. The validate warning asked for at the end (H3 on a box too small for it) is still worth deciding in triage.

  3. dkackman commented on Oct 14, 2026

    @dkackman
    OwnerAuthor

    triage: work — scope is the cancel path: a cancel during loading (model load/download, which never reaches the worker's step-boundary context.cancel() check) must take effect in bounded time (e.g. terminate and respawn the worker after a grace period), and the job reply must show the pending cancel; update the cancel_job docstring. Not in scope: the closing "warn before H3 on a 64 GB box" ask — that is a new validate_workflow rule and would need Don's sign-off as its own issue; do not build it here. Verify on lem with an uncached/slow-loading pipeline. No duplicate found. (claude-opus-5-5 / anthropic)

  4. dkackman commented on Oct 14, 2026

    @dkackman
    OwnerAuthor

    Fixed in 07f5612 (merged to develop, deployed to lem 04:38; server healthy). Worked as claude-sonnet-5-5 (anthropic).

    What changed:

    • Pending cancel is visible: get_job/list_jobs summaries carry cancel_requested: true while a cancelled job is still running.
    • Bounded time: cancel_job on a running job now starts a 30 s timer. If the job is then still in loading or a task phase (the phases with no checkpoint), the worker process is terminated and the job ends cancelled. The next job respawns the worker with a cold model cache, so cached models are lost. In an interruptible phase (generating, etc.) nothing is killed; the cooperative cancel applies as before.
    • Docstring: cancel_job now says a load is killed after 30 s and cancel_requested shows until then.
    • Not built: the validate warning for H3 on a small box, as triage said.

    Tests: real-path tests in tests/test_worker_pool.py (escalation in a loading phase, none in an interruptible phase, a dead worker under a requested cancel reads cancelled). The full suite has 18 failures, including kandinsky6 template/skill tests that also fail on a clean develop. The last full run was before the final docstring trim, so the docstring-budget failure it showed is fixed but not re-run in full.

    To verify on lem: start a slow or uncached load, call cancel_job, and check that cancel_requested: true shows at once and the job reads cancelled about 30 s later. Not run on a real model load myself.

    Proposed regression case (regression-suite-complete.md): that same call sequence, expecting cancelled within about 60 s.

  5. dkackman commented on Oct 14, 2026

    @dkackman
    OwnerAuthor

    Architecture review queued by the loop driver: this hand-off changed dw/ or dw_mcp/ on develop (b25f6b407a..31e4501304: dw/acceleration_warnings.py dw/server/job_record.py dw/server/job_results.py dw/server/jobs.py dw/validation.py dw/worker_manager.py dw_mcp/tools_jobs.py). The reviewer checks it against docs/ARCHITECTURE.md before the tester verifies.

  6. dkackman commented on Oct 14, 2026

    @dkackman
    OwnerAuthor

    Architecture review: pass (claude-opus-5-5 via anthropic).

    Read: a6c9a5a3, the only #892 commit in b25f6b40..31e45013 (dw/server/job_record.py, dw/server/job_results.py, dw/server/jobs.py, dw/worker_manager.py, dw_mcp/tools_jobs.py), against docs/ARCHITECTURE.md and stage-c-guardrails item 3.

    • Second owner: none. The escalation's phase test imports NON_INTERRUPTIBLE_PHASES from dw/events.py, which owns it, and doesn't redefine the list. Cancel stays in JobManager and the worker process stays in WorkerManager. The crash→CANCELLED mapping is in consume_results, which the Job queue row assigns to fold worker outcomes. The Worker pool dispatch row's rule ("cancel or a crash touches only the worker running that job") still holds: _terminate_if_stuck terminates only slot.manager for the job's own slot.
    • Build vs. buy: none. The grace timer is stdlib threading.Timer, and nothing in pyproject.toml covers it.
    • Map: no new module and no rule moved between modules, so no row is due. Optional: the Worker pool dispatch row could name the escalation test.
    • Context: the rationale is in the docstrings (JobManager._escalate_cancel, WorkerManager.terminate_worker). No CLAUDE.md changed.

    Non-blocking note: WorkerManager.terminate_worker repeats the terminate → join → kill sequence that shutdown_worker already runs, in the same module. It isn't a second owner, but whoever next edits worker teardown could factor it into one helper.

    Nothing was waived (the issue has no arch-approved). Back to the tester for the lem verify.

  7. dkackman commented on Oct 14, 2026

    @dkackman
    OwnerAuthor

    Verified on lem (develop @ fb829e7) by the tester, claude-opus-5-5 via anthropic.

    Load-phase cancel (the fix):

    • validate_workflow(name="templates/interpolate-frames") → downloads_required: genmo/mochi-1-preview 124.3 GB (uncached on lem).
    • run_workflow(...) with a bound ack (downloads: ["genmo/mochi-1-preview"], wait_seconds=10) → job 725a5ccd94a4, phase: loading, phase_detail: "downloading genmo/mochi-1-preview: 149.5 MB, 29.9 MB/s".
    • cancel_job("725a5ccd94a4") → {"status": "running"}. Straight afterwards, get_job → status: running, phase: loading, cancel_requested: true. list_jobs(limit=1) also shows cancel_requested: true.
    • wait_for_job(timeout_seconds=90) → status: cancelled after 29.1 s of waiting, about 31 s after the cancel. finished_at − started_at = 42.7 s. error: null, empty manifest.
    • get_health afterwards → status: ok. cuda:0 shows alive: false (the killed worker; on-demand, so it respawns with the next job), and cuda:1 was untouched.

    Adjacent case, interruptible phase (no kill expected): I ran templates/minimax/video-with-audio twice: warm, then after clear_memory. On lem the H3 load takes about 5 s (events: loading at 0 s, LoRA at 4 s, generating at 5 s), so both cancels landed in generating. Both jobs read cancelled within 6 s and 12 s, through the cooperative path. Neither was killed: cuda:0 stayed alive between them.

    The cancel_job docstring now says "a load is killed after 30 s (cancel_requested till then)". ✔

    Notes, not blocking:

    • The slim job in wait_for_job/run_workflow(wait_seconds) replies doesn't carry cancel_requested, though get_job/list_jobs do. A consumer that only waits sees just running until the kill, which is at most about 30 s.
    • I didn't run a follow-up job to watch cuda:0 respawn, because health shows the slot idle and the implementer's tests cover respawn.
    • The cancelled download left a partial (~0.5–1 GB) genmo/mochi-1-preview in lem's HF cache.

    No regression case added: the proposed sequence needs an uncached model, and on lem that means starting a 124 GB download on every run, which also leaves partial cache behind. That fits a deliberate one-off verify, not a recurring suite case.

  8. added
    status:verifiedTester confirmed the fix via a real MCP call
    and removed on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions