Conversation
…ueue stuck prints
## Problem
`_STUCK_JOB_TIMEOUT_SECONDS` was hard-coded to 7200 s (2 h), and a PRINTING
job over that cap was treated as hung and handed to `_requeue_or_fail`, which
reset it to QUEUED and re-dispatched **the same file** — without checking
whether the printer was still physically printing it.
Real-world damage (2026-09-13, Snapmaker U1): a 2h05m print was judged stuck
at the 2h cap, re-queued, and re-dispatched; the same object was physically
printed three times and the second plate dropped onto the first one's
finished part.
A stuck-timeout is a queue-side guess ("printer may be disconnected or
hung"), not a machine verdict. Re-dispatching a job that may still be
printing is the duplicate-print accident itself. A genuinely hung machine
needs a human; the dispatch-failure retry path (`_requeue_or_fail`) is
unchanged and still handles transient start failures.
## Changes
- `kiln/src/kiln/scheduler.py`
- `_STUCK_JOB_TIMEOUT_SECONDS` reads env `KILN_STUCK_JOB_TIMEOUT_SECONDS`
(default 43200 = 12 h) via the existing `kiln.parse_float_env` helper,
matching `queue.py`'s `KILN_STUCK_JOB_TIMEOUT_MINUTES` pattern (that
knob's `check_stuck_jobs()` currently has no caller — dead code — noted
for follow-up rather than silently removed in this patch).
- The stuck branch no longer calls `_requeue_or_fail`. It now mirrors the
existing permanent-failure path exactly: pop retry bookkeeping, discard
the seen-printing mark, `mark_failed`, publish `JOB_FAILED`, record the
resolution locally via `_auto_record_outcome` (contribute=False — the
stuck-timeout says nothing about the model, so it never federates),
and append to `tick()`'s `failed` report so callers see the verdict.
- `kiln/tests/test_scheduler.py`
- `TestStuckJobTimeout`: a PRINTING job over the timeout is FAILED with no
second dispatch (then or on a later tick); a JOB_FAILED event is
published and no JOB_SUBMITTED/re-queue; env override (1800 s) declares a
30-min-old job stuck; a garbage env value falls back to the 12 h default;
a valid env value is read at module load; a dispatch failure with retries
remaining still goes through the retry path.
- Updated `test_stuck_timeout_guess_contributes_nothing`'s fixture
(7300 s → 43300 s) because the new 12 h default no longer triggers at 2h05m.
|
|
|
Thanks for this, and sorry about the U1 prints. I read the branch on main and your diagnosis is exactly right: the stuck check only fires while the printer is reporting PRINTING, then hands the job to the retry path, which resets it to QUEUED and re-dispatches the same file the moment the machine goes idle. Any queued print over 2 h hit it. Want this in. Three things before merge:
Agreed on the rest: the wall-clock timeout is still a guess (a 13 h print would now be marked failed while still running), so the real fix is a no-progress-for-N-minutes detector, and |
f366b94 to
65b3bed
Compare
fix(scheduler): make stuck-job timeout env-overridable and never re-queue stuck prints
Summary
A PRINTING job over the hard-coded 2 h stuck-timeout was handed to
_requeue_or_fail, which reset it to QUEUED and re-dispatched the samefile — without checking whether the printer was still physically printing
it. This PR makes the timeout env-overridable (default 12 h) and makes the
stuck branch permanently fail the job instead of re-queuing it.
Real-world damage (why "re-queue on timeout" is wrong)
2026-09-13, Snapmaker U1: a 2h05m print was judged stuck at the 2 h cap →
_requeue_or_failreset the job to QUEUED and re-dispatched the same file →the same object was physically printed 3 times, and the second plate dropped
onto the first plate's finished part.
The stuck-timeout is a queue-side guess ("printer may be disconnected or
hung"), not a machine verdict. The machine's own words are in
error_msg; the queue cannot know whether the printer is still mid-print.Re-dispatching a job that may still be printing is the duplicate-print
accident. A genuinely hung machine needs a human to recover it; the queue's
job is to stop guessing on its behalf.
The dispatch-failure retry path (
_requeue_or_failfor start_print failures,printer errors, unregistered printers) is untouched — those re-queue decisions
are made on evidence the print never started, which is a different situation.
Changes
kiln/src/kiln/scheduler.py_STUCK_JOB_TIMEOUT_SECONDS: 7200 (hard-coded) → envKILN_STUCK_JOB_TIMEOUT_SECONDS, default 43200 (12 h), read via theexisting
kiln.parse_float_envhelper (same pattern asqueue.py'sKILN_STUCK_JOB_TIMEOUT_MINUTES)._requeue_or_fail. Mirrors the existingpermanent-failure path: pop retry bookkeeping, discard seen-printing,
mark_failed, publishJOB_FAILED, record the resolution locally via_auto_record_outcomewithcontribute=False(the stuck-timeout saysnothing about the model, so it never federates to the community pool),
and append to
tick()'sfailedreport so callers see the verdict.kiln/tests/test_scheduler.pyTestStuckJobTimeout:tick()reports it infailed,and there is no second dispatch (same tick or any later tick);
JOB_FAILEDevent and noJOB_SUBMITTED(the requeuepath's fingerprint);
KILN_STUCK_JOB_TIMEOUT_SECONDS=1800declares a30-min-old PRINTING job stuck (it is under the legacy 2 h cap);
preserved).
test_stuck_timeout_guess_contributes_nothingfixture updated(7300 s → 43300 s): with the new 12 h default, a 2h05m print is no
longer over the timeout.
Related dead code (not changed in this PR)
queue.pyalready has aKILN_STUCK_JOB_TIMEOUT_MINUTESenv knob feedingcheck_stuck_jobs(), but nothing callscheck_stuck_jobs()— the knob iscurrently dead. This PR intentionally does not touch it (different module,
different timeout, and consolidating it deserves its own change); flagged
here for a follow-up: either wire
check_stuck_jobs()into a poller orremove the unused knob.
Test plan
pytest kiln/tests/test_scheduler.py— 85 passed (79 pre-existing +6 new; 1 pre-existing fixture updated for the new default)
ruff check kiln/src/kiln/scheduler.py kiln/tests/test_scheduler.py— cleanpytest kiln/tests/ -q -n auto— see CI runVerification on a live install
The identical semantics change (env knob + never-requeue stuck branch) has
been running on a production kiln3d 1.4.1.1 install since 2026-09-14
(applied via a local patch; this PR is the upstreamable form of that fix).