Repository navigation
Drain result_queue before dispatch to prevent runtime deadlock in LocalExecutor - #70545
Conversation
f03a04a to
df19a88
Compare
|
@eladkal @amoghrajesh — all CI checks pass. Fixes a LocalExecutor shutdown deadlock. Ready for maintainer review. |
|
LGTM! should be good to merged post code owner review |
|
Moving to 3.3.2 as this is still pending code owner review and do not want to rush on merging this as it not critical |
|
@vatsrahul1001 thanks for the approval and for moving this to the 3.3.2 milestone. @XD-DENG @ashb @o-nikolas @pierrejeambrun @hussein-awala @dheerajturaga CI is green; PR needs a code owner sign-off to merge. It is a relatively small fix; could one of you take a look when you have a moment? |
Seems it wasn't included in 3.3.2. And looks like there are no deadlines for 3.3.3 or 3.4.0 For our system (using LocalExecutor), it is a bit urgent since it means there is always a risk of a deadlock happening (stopping all our DAGs) |
|
@vatsrahul1001 CC on this |
ac7fd59 to
a4e3b06
Compare
|
Rebased onto current Correction: an earlier comment described this as a shutdown deadlock fix. That was inaccurate — this PR addresses the runtime / dispatch path ( Why: workers can block writing results into a full Change: one line in Tests (run locally on this branch,
I have not verified a production deployment — this is a synthetic reproduction that passes with the patch. Feedback on the approach is welcome. |
…e dispatch deadlock Fixes the runtime/dispatch-path deadlock reported in apache#70526: during _process_workloads the scheduler could block in activity_queue.put() while workers were blocked writing results into a full result_queue pipe, a circular wait. Draining the result queue before each put breaks the cycle. The end()/join path is already handled by apache#67881; this covers normal dispatch. Adds a call-order unit test and a bounded real-queue regression test.
a4e3b06 to
22c2219
Compare
Backport successfully created: v3-4-testNote: As of Merging PRs targeted for Airflow 3.X In matter of doubt please ask in #release-management Slack channel.
|
…event runtime dispatch deadlock (#70545) (#74949) Fixes the runtime/dispatch-path deadlock reported in #70526: during _process_workloads the scheduler could block in activity_queue.put() while workers were blocked writing results into a full result_queue pipe, a circular wait. Draining the result queue before each put breaks the cycle. The end()/join path is already handled by #67881; this covers normal dispatch. Adds a call-order unit test and a bounded real-queue regression test. (cherry picked from commit b00cfeb) Co-authored-by: Saksham Kapoor <sakshamkapoor2911@gmail.com>
…event runtime dispatch deadlock (#70545) (#74949) Fixes the runtime/dispatch-path deadlock reported in #70526: during _process_workloads the scheduler could block in activity_queue.put() while workers were blocked writing results into a full result_queue pipe, a circular wait. Draining the result queue before each put breaks the cycle. The end()/join path is already handled by #67881; this covers normal dispatch. Adds a call-order unit test and a bounded real-queue regression test. (cherry picked from commit b00cfeb) Co-authored-by: Saksham Kapoor <sakshamkapoor2911@gmail.com>
…e dispatch deadlock (apache#70545) Fixes the runtime/dispatch-path deadlock reported in apache#70526: during _process_workloads the scheduler could block in activity_queue.put() while workers were blocked writing results into a full result_queue pipe, a circular wait. Draining the result queue before each put breaks the cycle. The end()/join path is already handled by apache#67881; this covers normal dispatch. Adds a call-order unit test and a bounded real-queue regression test.
Draining the result queue once before each put did not remove the deadlock reported in apache#70526. A worker can write a result between the drain and the put; if the activity pipe is full at that point the put blocks, and the worker keeps writing results until the result pipe is full too, so each side waits on the other. Linux pipes free space only a page at a time, which is why the regression test from apache#70545 hangs on loaded CI runners but not on macOS. Make the put on a short-lived helper thread and keep reading results on the main thread until it completes, so the result pipe can never fill up behind a blocked put. The workload is registered as running before it is sent, so a "running" result read while the put is in flight is not dropped. Generated-by: Claude Opus 5
* Keep draining LocalExecutor results while a workload put is blocked Draining the result queue once before each put did not remove the deadlock reported in #70526. A worker can write a result between the drain and the put; if the activity pipe is full at that point the put blocks, and the worker keeps writing results until the result pipe is full too, so each side waits on the other. Linux pipes free space only a page at a time, which is why the regression test from #70545 hangs on loaded CI runners but not on macOS. Make the put on a short-lived helper thread and keep reading results on the main thread until it completes, so the result pipe can never fill up behind a blocked put. The workload is registered as running before it is sent, so a "running" result read while the put is in flight is not dropped. Generated-by: Claude Opus 5 * Undo a failed LocalExecutor put and spawn workers while one is blocked Registering a workload before its put meant a put that raised left the key running and counted although nothing was sent; restore the queued workload and its bookkeeping instead. Counting each workload before its put keeps a worker from decrementing the outstanding count below zero when a batch is interrupted part way. A blocked put can only complete once a live worker reads the activity pipe, but workers were only spawned or replaced after the batch, so with the spawn start method (or once every worker had died) a batch larger than the pipe waited forever. Spawn them from the drain loop whenever fewer than parallelism are alive: spawn starts one per call, so a single busy worker would otherwise hold the put until its task finished. Generated-by: Claude Opus 5 * Cover undoing a failed put while an earlier dispatch of the key is running The test only reached the no-earlier-dispatch path, so the remaining > 0 branch of _undo_dispatch had no coverage. Generated-by: Claude Opus 5
…5086) (#76223) * Keep draining LocalExecutor results while a workload put is blocked Draining the result queue once before each put did not remove the deadlock reported in #70526. A worker can write a result between the drain and the put; if the activity pipe is full at that point the put blocks, and the worker keeps writing results until the result pipe is full too, so each side waits on the other. Linux pipes free space only a page at a time, which is why the regression test from #70545 hangs on loaded CI runners but not on macOS. Make the put on a short-lived helper thread and keep reading results on the main thread until it completes, so the result pipe can never fill up behind a blocked put. The workload is registered as running before it is sent, so a "running" result read while the put is in flight is not dropped. Generated-by: Claude Opus 5 * Undo a failed LocalExecutor put and spawn workers while one is blocked Registering a workload before its put meant a put that raised left the key running and counted although nothing was sent; restore the queued workload and its bookkeeping instead. Counting each workload before its put keeps a worker from decrementing the outstanding count below zero when a batch is interrupted part way. A blocked put can only complete once a live worker reads the activity pipe, but workers were only spawned or replaced after the batch, so with the spawn start method (or once every worker had died) a batch larger than the pipe waited forever. Spawn them from the drain loop whenever fewer than parallelism are alive: spawn starts one per call, so a single busy worker would otherwise hold the put until its task finished. Generated-by: Claude Opus 5 * Cover undoing a failed put while an earlier dispatch of the key is running The test only reached the no-earlier-dispatch path, so the remaining > 0 branch of _undo_dispatch had no coverage. (cherry picked from commit 1d1d17b) Generated-by: Claude Opus 5 Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
…5086) (#76223) * Keep draining LocalExecutor results while a workload put is blocked Draining the result queue once before each put did not remove the deadlock reported in #70526. A worker can write a result between the drain and the put; if the activity pipe is full at that point the put blocks, and the worker keeps writing results until the result pipe is full too, so each side waits on the other. Linux pipes free space only a page at a time, which is why the regression test from #70545 hangs on loaded CI runners but not on macOS. Make the put on a short-lived helper thread and keep reading results on the main thread until it completes, so the result pipe can never fill up behind a blocked put. The workload is registered as running before it is sent, so a "running" result read while the put is in flight is not dropped. Generated-by: Claude Opus 5 * Undo a failed LocalExecutor put and spawn workers while one is blocked Registering a workload before its put meant a put that raised left the key running and counted although nothing was sent; restore the queued workload and its bookkeeping instead. Counting each workload before its put keeps a worker from decrementing the outstanding count below zero when a batch is interrupted part way. A blocked put can only complete once a live worker reads the activity pipe, but workers were only spawned or replaced after the batch, so with the spawn start method (or once every worker had died) a batch larger than the pipe waited forever. Spawn them from the drain loop whenever fewer than parallelism are alive: spawn starts one per call, so a single busy worker would otherwise hold the put until its task finished. Generated-by: Claude Opus 5 * Cover undoing a failed put while an earlier dispatch of the key is running The test only reached the no-earlier-dispatch path, so the remaining > 0 branch of _undo_dispatch had no coverage. (cherry picked from commit 1d1d17b) Generated-by: Claude Opus 5 Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Fixes a runtime deadlock in LocalExecutor where the scheduler blocks indefinitely on activity_queue.put() during task dispatch, a gap left by #67881 which only addressed the shutdown path.
Workers fill the result_queue pipe buffer and block on put(). Blocked workers stop consuming activity_queue, so the scheduler blocks on activity_queue.put() and never reaches _read_results() to drain the pipe — a circular deadlock.
The fix: drain result_queue via _read_results() before each activity_queue.put() in _process_workloads, using the same drain-before-write pattern already applied to end().
closes: #70526
Was generative AI tooling used to co-author this PR?
Generated-by: OpenCode (DeepSeek v4 Pro) following the guidelines