Skip to content

fix(cron): external-worker one-shot runs no longer book failed on a half-written ack (#116164, salvage #107678) - #116689

Merged
teknium1 merged 2 commits into
mainfrom
fix/b0919-L06-cron-fireclaim
Sep 20, 2026
Merged

teknium1 merged 2 commits into
mainfrom
fix/b0919-L06-cron-fireclaim

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

One-shot cron runs dispatched to a restart-safe external worker are no longer booked failed with "Fire claim ownership lost before terminal completion." when the worker's ready-acknowledgement is read while half-written: the ack file is now published atomically.

  • cron/scheduler.py::_run_external_worker_payload writes the ack JSON to a per-pid .tmp sibling, fsyncs it and os.replace()s it into place (contributor commit, @keenvc). The gateway's exists()-then-read poll (_spawn_external_cron_worker, 50 ms) can only ever observe the ack fully absent or fully written; the "published an unreadable acknowledgement; treating handoff as ownership-uncertain" branch that terminalized a successful, delivered run as failed is no longer reachable from a normal handoff.
  • One invariant test drives the production entry point _run_external_worker_payload and asserts the ack is not observable while its body is being produced and that no temp residue remains (red on origin/main: O_CREAT made the empty file visible before json.dump).
  • contributors/emails/ mapping for the contributor's commit email.

Live repro (real python -m cron.scheduler --external-worker-file on a scratch HERMES_HOME, parent-style tight poller)

origin/main this head
ack observed as 0-byte file → JSONDecodeError → "unreadable acknowledgement" 3/3 runs 0/5 runs
ack read complete, execution_id matches 0/3 5/5

Root cause: the worker created the ack with O_CREAT|O_EXCL and then wrote the body into the same path, so the file existed empty for the window between open() and json.dump(), which the parent's exists-then-read poll hit.

The reporter's "form 2" ("Timed out waiting for local fire fence … failing closed" while delivery holds the fence past the 30 s lock wait) is a different mechanism and is handled with #116136.

Fixes #116164
Salvages #107678 (@keenvc) — commit cherry-picked with authorship preserved.

Dropped hunks

None — the contributor's single commit is applied in full; the test is ours.

Infographic

Cron worker ack is published atomically

keenvc and others added 2 commits September 19, 2026 21:26
_run_external_worker_payload wrote the acknowledgement JSON directly
into ack_path after creating it with O_CREAT|O_EXCL. That made an
empty 0-byte file visible to the scheduler's exists()-then-read
polling loop (every 50ms) before the JSON body was flushed, so the
poller would occasionally read an empty file and crash with
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
(observed 3x on 2026-09-09 in production logs).

Fix: write the ack payload to a per-pid temp file in the same
directory, then os.replace() it into place. Readers now only ever
see ack_path fully absent or fully written.
…envc's author mapping

The worker ack file is what the gateway's exists()-then-read loop keys on;
the test drives the production entry point _run_external_worker_payload and
asserts the ack is never observable while its body is still being written
(red on origin/main: O_CREAT made the empty file visible before json.dump),
and that no temp residue is left beside it.

Live probe (real `python -m cron.scheduler --external-worker-file` on a
scratch HERMES_HOME, tight parent-style poller): origin/main 3/3 runs read
an empty ack -> "unreadable acknowledgement"; this head 5/5 read a full ack.

Refs #116164 (form 1), #107184. Salvages #107678 (@keenvc).
@github-actions

github-actions Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on b577683 — test(cron): one invariant for the atomic external-worker ack

debug info

CI timings

CI timings · View report · View job

Wall time 6m12s vs 6m29s (-4.4%). 1 job(s) slower, 9 faster, 2 unchanged.

  • Check no case-colliding filenames / check-case-collisions: -36.0s
  • Python lints / Windows footguns (blocking): -18.0s
  • OS-specific tests / macOS-only tests: -11.0s
  • OS-specific tests / Windows-only tests: -9.0s
  • Profile artifact check / Reject profile archives: -4.0s

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cron Cron scheduler and job management labels Sep 20, 2026
@teknium1
teknium1 merged commit fc6cf2b into main Sep 20, 2026
37 checks passed
@teknium1
teknium1 deleted the fix/b0919-L06-cron-fireclaim branch September 20, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

One-shot cron job that completes and delivers successfully is recorded as failed: "Fire claim ownership lost before terminal completion."

3 participants