Repository navigation
Conversation
… log) After adopt, a worker killed without terminalizing (e.g. systemd OOM-kill) was recovered to unknown while the job record kept the previous run's values, no incident opened, nothing delivered, nothing logged. The waiter now does gateway-side bookkeeping: error log, failure incident, failure delivery, and an owner-fenced mark_job_run(False) carrying the exit code. Fenced by the launch-time fire owner, so replacement claims are never touched and repeats are no-ops. Closes NousResearch#120328.
BlockingThe bookkeeping sits at only one of the four exits a dead adopted worker can take — a competing reaper wins the
That ordering is reachable in-process: recovery also runs from the tick's dead-owner reaper ( Reproduced by pinning MinorThe exit code sits inside the incident-signature window, so a flapping kill mints an incident per code and resets the alert cooldown —
|
A competing dead-owner reaper (tick reap / manual pre-dispatch) can move the killed worker's row to ``unknown`` before the waiter's own recovery runs; both terminal exits then returned "fine" without the job bookkeeping (NousResearch#120328 follow-up). Hand every terminal record the waiter sees to ``_record_killed_worker_run`` (still a no-op unless the row is ``unknown`` and the launch claim stands), with a None returncode for the wait-timeout exit where the process has not exited yet. Also exclude the exit code from the incident signature: a kill that flaps between signals (137 vs 143) must refresh one incident per recovery reason rather than mint a fresh row per code and reset the repeat-alert cooldown.
|
Addressed the automated review (53b4f49): Blocking — bookkeeping reachable from every exit. Both terminal exits now hand the record they see to Minor — exit code out of the signature window. Tests: |
After adopt, a worker killed without terminalizing (e.g. systemd OOM-kill of its scope) was recovered to `unknown` while the job record kept the previous run's values: no incident, no delivery, no gateway log. Every job-level surface said the run never happened.
The external-worker waiter now does gateway-side bookkeeping on that path: gateway error log, failure incident (`_upsert_incident_for_failure`), failure delivery (`_deliver_crash_failure` with the run's adapters/loop, threaded through from `run_one_job`), and an owner-fenced `mark_job_run(False)` carrying the execution error plus the worker exit code.
Safety: only runs when the recovered record is `unknown` (a worker that terminalized itself keeps its own bookkeeping); fenced by the launch-time fire owner, so a replacement claim is never touched; the first mark consumes the claim, making repeats no-ops; the helper never raises, preserving the waiter's contract.
Tests: new `tests/cron/test_cron_killed_worker_job_update.py` failed on main (job `last_run_at` stayed None) and passes with the fix. Neighbor suites `test_restart_safe_worker` (2 assertions updated for the intentional `(job, adapters, loop)` handoff shape), `test_cron_incidents`, `test_cron_failure_deliver` green; the one remaining failure (`test_scoped_wrapper_exit...`, `os.getuid`) fails identically on pristine main on Windows.
Closes #120328.