Repository navigation
Conversation
|
Independently verified against current RED on base — your test file applied verbatim to The completed-response case fails exactly where you describe: no output saved, and the terminal bookkeeping takes the interruption path. The two control cases pass on base by construction (nothing written, nothing marked), which is what makes them controls. GREEN with the fix — same tree plus the The ordering assumption holds at the only production construction site. The split your helpers make is positional ( Overlap map. This is adjacent to, but distinct from, the open #113283 ( What stays open. The change makes the bookkeeping honest for completed runs; it does not establish why the heartbeat saw a loss on a 90-100s run with one gateway, a 300s TTL and a 180s grace. If the claim really is being re-owned or cleared mid-run by the worker-scope lifecycle, runs that are fenced during a side effect will still be recorded as lost — correctly, but invisibly. As a follow-up, the heartbeat's failure branch ( |
|
Blocking: the completed-run regression does not exercise the claim-loss branch reported by #113357. The heartbeat sets the loss event after heartbeat_fire_claim returns false because the stored claim is missing or owned by another runner. The new test instead sets the event inside run_job and makes every durable revalidation return true. It proves recovery from a stale signal, but not recovery from the reported owner mismatch. I ran the same completed-response path on this exact head with heartbeat_result=False. It saved no output, did not call mark_job_run, and finished with Fire claim ownership lost; stale result was discarded. The reported completed run therefore still ends as a false failure while the stored claim remains missing or replaced. Please add a regression for that path and fix the lifecycle operation that clears or re-owns the live claim. If this PR is intentionally limited to a transient heartbeat signal that later revalidates, narrow the issue link and do not close #113357 as fixed. |
1329531 to
1484e23
Compare
1484e23 to
71827ed
Compare
…at sample misses Long-running cron jobs (>60 s, i.e. past one heartbeat interval) intermittently ended with last_status=error / "Interrupted by shutdown before terminal completion." while their output was complete. The fire-claim heartbeat thread took ONE sample that read the claim as not ours, latched lost_ownership, and _FireOwnership.lost() then trusted that latch without asking the store again — so a run whose claim still validated (the very condition under which _record_fire_ownership_lost writes that message) was recorded as interrupted, its output never saved. The reporter's own error string proves the miss was transient: it is only ever written when the owner re-validates True. Why this shape: - _FireOwnership.lost(): an explicit transport cancel stays terminal; a latched heartbeat miss is re-checked against the store, and a claim that still validates keeps the run's real outcome. The owner-fenced mark_job_run and fire_claim_fence remain the authority, so a genuinely re-owned claim still yields (no delivery, no terminal write over the new owner, ledger discard). An unreachable store after a latched miss stays fail-closed. - _heartbeat_loop: a miss is re-sampled once after _FIRE_CLAIM_MISS_CONFIRM_SECONDS before it latches. Latching cancels the live agent run and ends the lease refresh, so a single sample must not do that; a genuinely re-owned claim misses twice and latches ~1 s later. The issue's other hypothesis (worker process identity under a systemd-run --scope worker) is falsified by the code: the owner token is stored in fire_claim.by at claim time and compared to the stored value, never recomputed from _machine_id(), and the heartbeat thread runs in a copied context so it reads the same store. Slimmer redo of #113364 (same direction: revalidate before trusting the latch) without its production-dead isinstance(_CombinedCancelEvent) branch. Co-authored-by: KoNit-K <konit.block@protonmail.com>
What does this PR do?
Fixes a cron bookkeeping race where a long-running job could finish with a complete response, then be changed to
Interrupted by shutdown before terminal completion.because a previously failed fire-claim heartbeat left its cancellation event set. The scheduler now revalidates the durable fire claim before treating that signal as terminal. A current owner records the completed result; a genuinely replaced or unavailable owner remains fenced out. Explicit transport cancellation remains an interruption even when the fire claim is still healthy.Related Issue
Fixes #113357
Type of Change
Changes Made
cron/scheduler.py— distinguish the scheduler fire-claim heartbeat event from an independent transport cancellation, then revalidate the current fire owner before converting a completed run into an interruption.cron/scheduler.py— keep an already-signaled ownership loss fail-closed when the revalidation cannot reach the store, while retaining the existing best-effort behavior for an initial validation error.tests/cron/test_shutdown_interrupt.py— cover a completed response after heartbeat loss, a genuinely lost owner with an incomplete response, and a healthy claim paired with transport cancellation.How to Test
Run the focused cron shutdown suite:
Result: 31 passed, 0 failed. Also run:
Result: all checks passed.
Evidence
BEFORE RED: the new completed-response regression failed on the prior implementation: the pending heartbeat event made
_FireOwnership.lost()return immediately, so output was never saved and terminal bookkeeping took the generic interruption path (1 failed, 1 control passed).AFTER GREEN: the full focused file completed with 31 passed, 0 failed. The completed-response case saves the full report, writes a successful owner-fenced terminal status, and finishes the execution successfully after ownership revalidation.
CONTROL: an incomplete response whose owner no longer validates saves no output and does not call
mark_job_run; its execution is recorded as a discarded stale result. A separate control confirms that a transport cancellation remains interrupted even when the claim heartbeat validates.The change classifier enables Python, Python production, scan, Docker, and Nix lanes for this production Python change. Local Python tests and Ruff were run above. Docker publish is not a local PR verification step, and Nix is unavailable on this macOS worker; those lanes remain CI follow-up.
Checklist
Fixes #113357.scripts/run_tests.sh tests/cron/test_shutdown_interrupt.pypasses with 31 tests.