Skip to content

Fix #571: drain dead worker queues before closing them#590

Open
nitrobass24 wants to merge 1 commit into
developfrom
fix/571-drain-dead-worker-queues
Open

Fix #571: drain dead worker queues before closing them#590
nitrobass24 wants to merge 1 commit into
developfrom
fix/571-drain-dead-worker-queues

Conversation

@nitrobass24

Copy link
Copy Markdown
Owner

Summary

Fixes #571. WorkerSupervisor.recreate_if_dead() swapped in a fresh worker and then closed the dead worker's result queues without draining them first, so extract/validate work the worker finished just before crashing was silently dropped — the sole consumer (ModelUpdater) read the fresh (empty) worker's queues after the swap.

The fix

  • Carry-over buffer in the supervisor. _drain_dead(dead) drains the dead worker's pop_completed/pop_failed queues into a buffer before _reap_dead() closes them (best-effort, per-queue isolated, inside try/finally so FDs are always freed).
  • Explicit pop_completed/pop_failed on the supervisor return buffered + fresh, then clear — surfacing carried results exactly once, ahead of the fresh worker's. Buffers extend so rapid back-to-back restarts accumulate rather than clobber.
  • ModelUpdater reads completed/failed through the supervisor (drops .worker); statuses stay on .worker (display-only).

Adversarial-review hardening

  • Read the live worker before clearing the buffer, so a failed live read can't drop the carry-over (it survives to a later cycle).
  • try/finally guarantees the dead worker's queues are closed even if draining raises.

Honest limitation

Items stuck in a dead child's unflushed multiprocessing.Queue feeder thread are unrecoverable; we recover everything the parent can still read — consistent with the #535 "redo, don't guarantee" recovery contract.

Note for reviewers

ModelUpdater previously read completed/failed via supervisor.worker.pop_*(); it now goes through the supervisor so buffered results are surfaced. The one existing test that asserted the dead worker is never read (test_pipeline_recreate_rewires_updater_reads) was updated to reflect the intended new behavior (dead worker drained exactly once).

Test plan

  • test_recreate_drains_dead_worker_results_before_close — fails before fix (returns []), passes after.
  • Added: exactly-once surfacing, rapid-restart accumulation, per-queue drain-failure isolation, buffer survival on a failing live read, reap-runs-on-drain-failure.
  • 494 controller unit tests pass (489 baseline + 5 new), 0 regressions.
  • pyright strict: 0 errors on worker_supervisor.py + model_updater.py.
  • ruff + C901 clean; methods within code-health targets.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@nitrobass24, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 48a16262-ba36-4113-858b-2a936f50c411

📥 Commits

Reviewing files that changed from the base of the PR and between 55be236 and 4198c81.

📒 Files selected for processing (3)
  • src/python/controller/model_updater.py
  • src/python/controller/worker_supervisor.py
  • src/python/tests/unittests/test_controller/test_worker_supervisor.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/571-drain-dead-worker-queues

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.

WorkerSupervisor.recreate_if_dead() swapped in a fresh worker and then
closed the dead worker's result queues without draining them, so extract/
validate work the worker finished just before crashing was silently
dropped (the sole consumer, ModelUpdater, read the fresh worker's empty
queues after the swap).

The supervisor now drains the dead worker's completed/failed queues into a
carry-over buffer before _reap_dead() closes them, and surfaces them on the
next pop_completed()/pop_failed() — exactly once, ahead of the fresh
worker's results. ModelUpdater reads completed/failed through the
supervisor (statuses stay on .worker, display-only).

Hardening from adversarial review:
- Read the live worker before clearing the buffer, so a failed live read
  does not drop the carry-over.
- try/finally guarantees the dead worker's queues are still closed (FDs
  freed) even if draining raises.

Tests: reproduce the drop, exactly-once surfacing, rapid-restart
accumulation, per-queue drain-failure isolation, buffer survival on a
failing live read, and reap-on-drain-failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nitrobass24 nitrobass24 force-pushed the fix/571-drain-dead-worker-queues branch from 4394079 to 4198c81 Compare June 29, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WorkerSupervisor: drain dead worker queues before close_queues() in recreate_if_dead()

1 participant