Skip to content

feat(celery): finalize in-flight task on worker eviction so the chord proceeds - #1285

Closed
ocervell wants to merge 5 commits into
mainfrom
feat/worker-eviction-finalize-main
Closed

feat(celery): finalize in-flight task on worker eviction so the chord proceeds#1285
ocervell wants to merge 5 commits into
mainfrom
feat/worker-eviction-finalize-main

Conversation

@ocervell

@ocervell ocervell commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Re-extracted from the closed #1203 — like the cap PR (#1284), this generic worker-reliability fix was folded into the ai-resiliency branch (#1241, AI-task hardening, base canary) and its standalone PR closed. It's general infra (applies to every runner, not AI), so it belongs on main independently. Cherry-picked clean onto current main (CI-retrigger noise commits dropped).

What it does

On a graceful worker shutdown (SIGTERM — k8s pod eviction / node drain / rolling update), worker_shutting_down sets a flag and the running task's monitor thread stops the task early and finalizes it — so the chord's mark_runner_completed still fires and the workflow proceeds, instead of the task hanging until the broker visibility timeout (hours on the long pool). Works on any pool (monitor-thread based). Per-task flag is cleared at start so a stale flag can't finalize a fresh task.

Validation

Exercised end-to-end earlier (SIGTERM → task stopped ~9s vs ~60s, chord proceeds, 5/5). tests/unit/test_eviction.py: 3 passed; test_celery.py: 17 passed on current main; flake8 clean.

Note

feature:worker-reliability. Companion: #1284 (worker-loss cap). #1202 (on_build identity) is already on main (in the pending 0.40.0).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added graceful shutdown handling for long-running workers so running jobs can detect eviction and stop sooner.
    • Improved monitoring responsiveness during shutdown checks to reduce the chance of stale work continuing after termination begins.
  • Bug Fixes

    • Cleared stale shutdown state at startup to prevent old shutdown signals from affecting new runs.
    • Added safeguards so interrupted tasks emit a warning and terminate cleanly instead of hanging.

ocervell and others added 5 commits July 6, 2026 19:20
… proceeds

On a K8s pod eviction (SIGTERM -> grace -> SIGKILL) the worker running a task
dies. With task_acks_late on the Redis broker, its message is only redelivered
after the broker visibility timeout (hours, on the long-task pool) — stalling the
surrounding chord/workflow that whole time. Unlike a child OOM (where the master
survives and task_reject_on_worker_lost requeues immediately), a whole-pod
eviction has no surviving master, so Redis can only fall back to the visibility
timeout.

Catch the worker_shutting_down signal and raise a flag the running task's monitor
thread polls. On shutdown it stops the task early via the existing stop_process
path, so the task returns its partial results through the normal completion path
and the chord proceeds in seconds instead of waiting for the visibility timeout.

- celery_signals.py: SHUTDOWN_FLAG + worker_shutting_down_handler (wired
  unconditionally); stale flag cleared on worker boot.
- command.py: the monitor thread checks the flag (same self-stop used for
  timeout/memory limits) and caps its poll interval (MONITOR_POLL_SECONDS=5) so
  an eviction is caught well within the pod's terminationGracePeriodSeconds.
- tests: flag lifecycle + a behavioral test (a running command stops early and
  emits the eviction warning).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5vSjfkBuGAAHdKxHS3ySm
…late test

The integration suite caught a real leak: SHUTDOWN_FLAG is a machine-global file,
so once any worker fired worker_shutting_down (e.g. between test files) the flag
persisted and every later task whose monitor polled once self-aborted — slow
tasks failed, fast ones that finished before the first poll passed. Prod never
hit it (1 task = 1 pod = fresh /tmp), but any shared/long-lived worker does.

- command.py: clear the flag at monitor start, so it only means "shutdown raised
  *during this run*", not a stale flag from a previous worker/task.
- celery_signals.py (CodeRabbit): ensure the flag's parent dir exists and catch +
  log OSError instead of swallowing all exceptions.
- test_eviction.py (CodeRabbit): isolate SHUTDOWN_FLAG to a per-test temp path;
  add a regression test that a flag set *before* a task starts does not stop it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5vSjfkBuGAAHdKxHS3ySm
…ace)

The behavioral test set the flag once after a fixed 2s wait, but the monitor now
clears any pre-existing flag at startup; on a slow CI runner the monitor started
*after* that single set and wiped it, so the task never stopped and the test
failed. Re-raise the flag in a loop until the task stops, so the monitor's poll
sees it once it is running regardless of start timing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5vSjfkBuGAAHdKxHS3ySm
The real-subprocess behavioral test was environment-dependent: in CI the monitor
thread didn't stop the live `sleep` reliably, so the test flaked (and the stale
test passed for the wrong reason). Replace both with deterministic tests that
exercise _monitor_process directly against a bare Command — one asserts it calls
stop_process(exit_ok=True) + emits the eviction Warning when the flag is set,
the other asserts it clears a stale flag at start (the integration-leak
regression). No subprocess, no timing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5vSjfkBuGAAHdKxHS3ySm
The real-file version of the stale-flag test flaked on CI (patched temp path +
bare-__new__ Command), while the real behaviour is already proven green by the
integration suite. Assert that _monitor_process calls clear_shutdown_flag() at
start via a mock instead of inspecting the filesystem — deterministic and
environment-independent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5vSjfkBuGAAHdKxHS3ySm
@ocervell ocervell added the feature:worker-reliability Celery worker reliability & redelivery label Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a803e7d4-6d4c-4c54-86bf-47aa9685232b

📥 Commits

Reviewing files that changed from the base of the PR and between 0203f02 and d10c332.

📒 Files selected for processing (3)
  • secator/celery_signals.py
  • secator/runners/command.py
  • tests/unit/test_eviction.py

Walkthrough

Adds a file-based shutdown flag mechanism triggered by Celery's worker_shutting_down signal, enabling the command monitor thread to detect worker eviction, terminate running processes gracefully with partial results, and reduces monitor polling interval. Includes new unit tests covering the flag lifecycle and monitor behavior.

Changes

Worker Eviction Self-Finalization

Layer / File(s) Summary
Shutdown flag helpers and signal wiring
secator/celery_signals.py
Adds SHUTDOWN_FLAG constant and is_worker_shutting_down, clear_shutdown_flag, worker_shutting_down_handler functions; wires handler to Celery's worker_shutting_down signal and clears stale flags in setup_handlers().
Monitor thread shutdown detection and pacing
secator/runners/command.py
Adds MONITOR_POLL_SECONDS constant; _monitor_process clears stale flags at start, checks is_worker_shutting_down() to stop the process with SIGTERM and enqueue a warning, and reduces the wait interval via min(CONFIG.runners.stat_update_frequency, MONITOR_POLL_SECONDS).
Unit tests for shutdown flag and monitor behavior
tests/unit/test_eviction.py
Adds TestEvictionSelfFinalize verifying flag set/clear lifecycle, monitor stopping a process on flag detection with eviction warning enqueued, and monitor clearing stale flags at startup.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • freelabz/secator#727: Introduces the Command monitor thread that this PR extends with shutdown-flag checks inside _monitor_process.

Poem

A signal comes, the pod must go,
but tasks don't vanish—oh no, no!
A flag is set, the monitor sees,
it stops the process with graceful ease. 🐇
Partial results still find their way,
the chord proceeds—hooray, hooray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: handling Celery worker eviction to finalize in-flight tasks.
Linked Issues check ✅ Passed The PR implements the requested shutdown flag, monitor polling cap, stale-flag clearing, and tests for eviction-driven early completion.
Out of Scope Changes check ✅ Passed The changes stay focused on Celery eviction handling and related tests, with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/worker-eviction-finalize-main

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ocervell
ocervell marked this pull request as draft July 6, 2026 17:27
@ocervell

ocervell commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Parked for later (owner decision). Concern: not enough confidence in the in-flight runner's behavior on worker shutdown — what happens to the running Python runner / external tool process, partial results, and how finalize-early interacts with redelivery. Too many edge cases to merge without more validation.

Safe to park: this only helps the graceful SIGTERM (k8s eviction / drain / rolling update) path — a hard worker death (SIGKILL/OOM) is handled by acks_late redelivery + the worker-loss cap (#1284) and the broker visibility-timeout fallback. So the platform works correctly without this; it just finalizes slower on graceful eviction. Revisit when we can validate the runner/partial-results semantics end-to-end.

@ocervell ocervell closed this Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature:worker-reliability Celery worker reliability & redelivery

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant