Skip to content

feat(printer): timer-driven one-shot supervisor replaces the resident per-printer process - #750

Merged
alex-mextner merged 5 commits into
mainfrom
feat/printer-ondemand-747
Nov 12, 2026
Merged

alex-mextner merged 5 commits into
mainfrom
feat/printer-ondemand-747

Conversation

@alex-mextner

Copy link
Copy Markdown
Owner

What

The printer supervisor no longer lives in memory between checks. 3d-printer-ondemand@NAME.timer fires a one-shot 3d-printer-ondemand@NAME.service 5 s after it is armed and 30 s after each check finishes. The check is python -S -m printer_ops.ondemand NAME (stdlib only, ~20 ms, exits). The heavy watch and vision workers run only while a print runs.

  • Parity with the resident supervisor: one request to the printer per check; workers start while a print runs and stop after the grace period (default 300 s); a print already running after a reboot is picked up at the first check; a printer that is unreachable is retried once at the next tick, then every 2 minutes, and never gets workers.
  • State: one small file per printer under $XDG_RUNTIME_DIR/3d-cli/ondemand/NAME.json (tmpfs, never disk), rewritten only when its content changes. The config loader runs in a child process only when a config file (or the selected fleet/config directory) changed; absent candidate files are tracked too, so a printer-devices.yaml created later is picked up.
  • Journal: LogLevelMax=notice removes systemd's own Starting/Finished lines; the check logs only on transitions.
  • Install / uninstall / status / doctor / deploy_check are updated: install retires the first-design resident process, uninstall stops the timer, a check in flight and the workers (and skips units systemd has no file for), status judges the check service's own finish time and result (a backoff tick does not make an old observation look fresh), a missing checkout skips the check silently via ConditionPathExists.
  • tests/test_printops_ha_stage.py::test_detect_uses_configured_url_known_names_and_never_writes is made hermetic (test_detect_uses_configured_url_known_names_and_never_writes depends on the host resolving homeassistant.local #748): it failed on any machine where homeassistant.local resolves.

Ticket: #747

Proof

Screenshots

(none) - no visual surface: command output and systemd units.

…n only while a print runs (Refs #747)

Idle, the four always-on units held 1.7 GiB (two ONNX vision processes), 4.6 % of a core and 104 file
writes a minute. Now a tiny always-on supervisor per printer (`3d-printer-ondemand@NAME`,
printer_ops/ondemand.py, stdlib only, ~18 MB, Type=notify with a watchdog, no file) asks the printer
one small question every 30 s (Moonraker: GET /printer/objects/query?print_stats&webhooks over a raw
socket; creality-stock: one ws:9999 read) and runs `systemctl --user start|stop` on the unchanged
watch and vision units: start while a print is printing or paused, stop completely a grace period
(default 300 s) after it. Unreachable printers back off (30 s, 60 s, 2 min) and never start workers;
a supervisor restart leaves running workers alone and reconciles at start; a print already running
starts the workers at the first poll.

- supervise_units: ondemand template (notify, watchdog, PYTHONPATH + `-m`, MemoryMax); worker
  templates lose [Install]; install enables only the supervisor and disables (never stops) workers an
  older install left enabled; uninstall stops the workers.
- supervise status / doctor: a stopped worker is right for an idle or switched-off printer and a FAIL
  for a printing one (a WARN within one poll lag of the supervisor's last poll, read from StatusText);
  --no-live uses the supervisor's StatusText; deploy_check no longer warns about a missing vision
  unit while the supervisor runs.
- `3d printer ondemand NAME [--poll S] [--grace S] [--once]`; timings also from
  THREED_ONDEMAND_POLL_S / THREED_ONDEMAND_GRACE_S (a systemd drop-in).
- deploy_check.render/run and doctor deployment._steps split into helpers (complexity gate, Refs #707).
…cycle and what status judges (Refs #747)

printer.md gets the On-demand supervision section (design, why polling and not a websocket, the
idle/active/grace machine, unreachable backoff, restart reconcile, timing drop-in, the bed-monitor
trade-off) and the `ondemand` command; the Always-on supervision tables, Liveness and the install notes
say which unit runs when; printer-vision.md, printers-doctor.md and AGENTS.md follow.
…stop of a worker is not a failure (Refs #747)

Found while deploying: stopping the vision worker with SIGTERM ends `uv run` with 143, which left the unit
`failed` and ran the failure hook after every print; the worker templates now carry SuccessExitStatus=143.
And workers started by hand or by the backups mount-retry timer while the printer is idle would run for
ever, because the supervisor only stopped what it had started: every 20 idle polls (10 minutes by default,
one `systemctl is-active`) it now adopts running workers and stops them after one grace period.
… per-printer process

A systemd timer runs a one-shot check per printer that starts or stops the vision/watch workers, so an idle
printer costs no resident memory. Includes install migration from the resident design, status/doctor support,
docs and tests.

Refs #747
@alex-mextner
alex-mextner merged commit b9fb559 into main Nov 12, 2026
10 checks passed
@alex-mextner
alex-mextner deleted the feat/printer-ondemand-747 branch November 12, 2026 02:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant