Repository navigation
fix(cron): late imports survive an in-place upgrade; a released claim records its outcome first - #121052
fix(cron): late imports survive an in-place upgrade; a released claim records its outcome first#121052djmango wants to merge 1 commit into
Conversation
…rd a released claim's outcome first Two cron executions (b20a472b7e80 at 17:00, f98d49b4524e at 17:07) were marked failed with "cannot import name 'safe_strftime' from 'hermes_time'" while both workers kept running to completion (output written 18:19 / 18:57). A long-running process had hermes_time cached from the revision that predated safe_strftime, then imported a consumer for the first time after the tree was upgraded in place at 18:12, so the import needed a NEW name from a module it already had cached. Move safe_strftime/_repair_surrogates into their own stdlib-only leaf module (hermes_time re-exports them for existing callers) so a post-upgrade import loads them from disk instead of from the stale cached module. Second: sweep_stale_inflight dropped the in-flight claim in its classification loop and only afterwards wrote the release record; for an "age" release of a finite-repeat job it wrote no durable record at all. A released claim could therefore leave no answer to "did side effects run?" (NousResearch#115692). Write the durable outcome BEFORE releasing — terminalising the claim's still-open ledger row via cron.executions.mark_claim_unknown — then release, re-checking the claim's start stamp so a fresh fire in the gap survives; if the write fails the claim is retained so the next sweep retries instead of losing the attempt. Tests: tests/cron/test_upgrade_module_skew.py reproduces the exact ImportError against a pre-upgrade hermes_time (and pins the failure signature), tests/cron/test_stale_claim_durable_terminal.py covers write-before-release ordering, fail-closed retention, and the durable ledger terminal state.
The module-skew half looks right: 1.
Exactly inverted. Chain to a double dispatch: 2. Minor: Unverified / please confirm: all |
Root cause (proven)
Two cron executions were recorded
failedwith an import error while their workers kept running to completion:b20a472b7e80failed—cannot import name 'safe_strftime' from 'hermes_time'f98d49b4524efailed— sameA long-running process had
hermes_timecached insys.modulesfrom the revision that predatedsafe_strftime, then imported a consumer module for the first time after the tree was upgraded in place at 18:12:46 (bulk mtime batch over 2862.pyfiles). That late import needed a new name from an already-cached module:Evidence, in order:
hermes_timeat the running revision vs the tree that landed mid-run:safe_strftime/_repair_surrogatesare the only names added (git show <old>:hermes_time.pyvsgit show <new>:hermes_time.py→diffshows exactly those two).cron/ledger.py,tests/cron/test_upgrade_module_skew.py): "a lazily imported module after an upgrade must not need new names from a module the daemon already has cached".Changes
hermes_time_format.py(new leaf module) holdssafe_strftime+_repair_surrogates. It imports nothing fromhermes_time, so a consumer first imported after an upgrade loads it from disk.hermes_timere-exports both names for existing callers, and the 12 consumers that importsafe_strftimenow import it from the leaf module.cron/scheduler.py,cron/executions.py):sweep_stale_inflightused to mutate the in-flight guard in its classification loop and only then write the release record — and for anagerelease of a finite-repeat job it wrote nothing durable. Now the outcome is written before the claim is dropped:cron.executions.mark_claim_unknown()terminalises the claim's still-open ledger row (unknown+ the#115692reason), then the release happens, re-checking the claim's start stamp so a fresh fire in the gap survives. If the write fails the claim is retained (fail closed) so the next sweep retries instead of losing the attempt.Tests
The new tests are RED on the parent commit (
6 failed, 6 passed), reproducing the production signature verbatim:Uncertain
error), but the mechanism is identical for all of them and the fix covers every module-level consumer — the only two names added tohermes_timein that upgrade are the two that moved.#115692) is unchanged by this PR: a live-but-wedged worker is still not killed. What is now guaranteed is that its claim always has a durable outcome on disk before it is released.