Skip to content

fix(cron): long fenced deliveries no longer recorded as failed 'ownership lost' (#108341, #100401; salvage #106745) - #109310

Merged
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:fix/cron-heartbeat-fire-fence-108341
Sep 12, 2026
Merged

kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:fix/cron-heartbeat-fire-fence-108341

Conversation

@kshitijk4poor

Copy link
Copy Markdown

A cron run whose delivery or agent turn holds the per-job fire fence for longer than 30s is no longer recorded as failed / "Fire claim ownership lost" after it actually succeeded.

Fixes #108341, fixes #100401. Salvage of #106745 by @KoNit-K (cherry-picked, authorship preserved); test shape from #100418 by @oheckmann74 and #108359 by @Sahilvishnaliya (Co-authored-by).

Root cause

The run thread holds _fire_job_lock (a per-thread RLock + flock) across delivery. The 60s cron-fire-claim-heartbeat thread renewed the claim through the same fence, blocked for _JOBS_LOCK_TIMEOUT_SECONDS (30s), failed closed to False, and the scheduler read that as a takeover: lost_ownership set, run interrupted, ledger row written failed while the message had already been delivered.

Change

  • cron/jobs.py heartbeat_fire_claim: refresh under _jobs_lock only (same shape as heartbeat_run_claim). _refresh_claim still compare-and-swaps on claim["by"] == expected_owner, so a real takeover keeps returning False. Docstring records why the fence is deliberately not taken.
  • tests/cron/test_claim_job_for_fire.py: one invariant test — worker thread holds fire_claim_fence, heartbeat on the calling thread returns True, heartbeat for a foreign owner returns False. Fails on unfixed jobs.py in ~2s.

Validation

Check main d62716c this branch
heartbeat while another thread holds the fence False after timeout True, ~0ms
heartbeat after claim.by takeover False False
E2E through scheduler._run_with_fire_claim_heartbeat, run holds fence 3s > heartbeat period lost_ownership set, "interrupting stale run" not set, claim retained
new test fails (1.9s) passes
scripts/run_tests.sh tests/cron/ — 1247 passed; 1 failure (test_ensure_hermes_home_sets_0700) reproduces on main, unrelated

Lock ordering audited: every fence+jobs_lock site is fence→jobs_lock; the heartbeat now takes strictly fewer locks, no new ordering. Remaining _under_fire_fence users (claim_job_for_fire, mark_job_run) mutate by and run on the owner thread — correct.

Related open PRs (same bug)

#95432 #97565 #100418 #100965 #102627 #104286 #105724 #106745 #107226 #107844 #108018 #108359 #108471 #109003 #109083 — this PR carries the smallest fix from that cluster; the tri-state/scheduler-side variants are unnecessary once the heartbeat no longer waits on the fence.

KoNit-K and others added 2 commits September 12, 2026 22:51
heartbeat_fire_claim only CAS-refreshes claim.at via _with_job; wrapping
_under_fire_fence across save_jobs let a blocked .jobs.lock pin the fence
and cause mark_job_run to fail closed on completed jobs.
Replace the POSIX-only jobs-flock contention test (skipped off-POSIX,
~120 LOC of monkeypatched flock plumbing) with a single invariant test
that fails on pre-fix code in <1s: hold the per-job fire fence from a
worker thread, assert the heartbeat still returns True on the calling
thread, and that a takeover is still detected (False). The docstring on
heartbeat_fire_claim now records WHY it is not under the fence, so the
next refactor does not put it back.

Co-authored-by: Oliver Heckmann <46627487+oheckmann74@users.noreply.github.com>
Co-authored-by: salch-cred <141555468+salch-cred@users.noreply.github.com>
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) September 12, 2026 17:23
@kshitijk4poor
kshitijk4poor merged commit 9a60a7f into NousResearch:main Sep 12, 2026
37 checks passed
This was referenced Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants