Fix #571: drain dead worker queues before closing them#590
Conversation
|
Warning Review limit reached
Next review available in: 58 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
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>
4394079 to
4198c81
Compare
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
_drain_dead(dead)drains the dead worker'spop_completed/pop_failedqueues into a buffer before_reap_dead()closes them (best-effort, per-queue isolated, insidetry/finallyso FDs are always freed).pop_completed/pop_failedon the supervisor returnbuffered + fresh, then clear — surfacing carried results exactly once, ahead of the fresh worker's. Buffersextendso rapid back-to-back restarts accumulate rather than clobber.ModelUpdaterreads completed/failed through the supervisor (drops.worker); statuses stay on.worker(display-only).Adversarial-review hardening
try/finallyguarantees the dead worker's queues are closed even if draining raises.Honest limitation
Items stuck in a dead child's unflushed
multiprocessing.Queuefeeder thread are unrecoverable; we recover everything the parent can still read — consistent with the #535 "redo, don't guarantee" recovery contract.Note for reviewers
ModelUpdaterpreviously read completed/failed viasupervisor.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.worker_supervisor.py+model_updater.py.🤖 Generated with Claude Code