[SPARK-57638][4.1][SQL] Avoid busy-waiting in Declarative Pipelines flow resolution - #57571
Open
LuciferYang wants to merge 1 commit into
Open
[SPARK-57638][4.1][SQL] Avoid busy-waiting in Declarative Pipelines flow resolution#57571LuciferYang wants to merge 1 commit into
LuciferYang wants to merge 1 commit into
Conversation
…esolution `DataflowGraphTransformer.transformDownNodes` resolves flows on a bounded thread pool and drives them from a `while` loop that, each pass, partitioned the in-flight futures with the non-blocking `future.isDone`, reaped the completed ones, and scheduled a new flow if a slot was free. When all slots were in flight (or the queue was drained and only the last futures remained) and none had completed, the pass reaped nothing and scheduled nothing, then looped again immediately - busy-spinning on `isDone` and pinning a core for the duration of resolution. This drives the loop with an `ExecutorCompletionService` instead: completed tasks are drained with the non-blocking `poll()`, and when nothing can be scheduled but tasks are still running, the loop blocks on `take()` until the next one finishes rather than spinning. Behavior is otherwise unchanged - the same flows are scheduled in the same order, exceptions are still propagated via `Future.get()`, and an `outstanding` counter replaces the `ArrayBuffer[Future]` for slot bookkeeping. Resolving a graph with more flows than the parallelism (10) kept one CPU core busy at 100% doing no useful work for the whole resolution, which is wasteful and shows up as unexplained driver CPU. No. Two new cases in `ConnectValidPipelineSuite` cover the regime this PR changes - more flows than `parallelism` (10), so the slots fill and the loop reaches the blocking `take()` branch that replaces the busy-wait. The small graphs in the existing suites never get there. - `resolution terminates and resolves all flows when flow count exceeds parallelism` - 25 independent flows. - `resolution re-queues retryable flows under load when consumers exceed parallelism` - 20 consumers registered before their source `src`, so the first batch throws `TransformNodeRetryableException`, parks as dependents of `src`, and is re-queued once `src` resolves; this exercises the retryable re-queue path together with the blocking branch. Both assert only the outcome (every flow resolves and the call returns), so they are deterministic and have no timing dependence - a regression that deadlocked would hang until the suite times out. Asserting the absence of a busy-wait directly is not included, since that requires CPU-time or timing measurements that are flaky in CI. Existing graph-resolution suites (`ConnectValidPipelineSuite`, `ConnectInvalidPipelineSuite`, `SqlPipelineSuite`, `TriggeredGraphExecutionSuite`, `MaterializeTablesSuite`) still pass; the change only affects how the loop waits, not what it resolves. Generated-by: Claude Code (Claude Opus 4.8) Closes apache#56700 from LuciferYang/sdp-resolution-busy-wait. Authored-by: YangJie <yangjie01@baidu.com> Signed-off-by: yangjie01 <yangjie01@baidu.com> (cherry picked from commit 0747e28)
uros-b
approved these changes
Jul 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes were proposed in this pull request?
DataflowGraphTransformer.transformDownNodesresolves flows on a bounded thread pool and drives them from awhileloop that, each pass, partitioned the in-flight futures with the non-blockingfuture.isDone, reaped the completed ones, and scheduled a new flow if a slot was free. When all slots were in flight (or the queue was drained and only the last futures remained) and none had completed, the pass reaped nothing and scheduled nothing, then looped again immediately - busy-spinning onisDoneand pinning a core for the duration of resolution.This drives the loop with an
ExecutorCompletionServiceinstead: completed tasks are drained with the non-blockingpoll(), and when nothing can be scheduled but tasks are still running, the loop blocks ontake()until the next one finishes rather than spinning. Behavior is otherwise unchanged - the same flows are scheduled in the same order, exceptions are still propagated viaFuture.get(), and anoutstandingcounter replaces theArrayBuffer[Future]for slot bookkeeping.Why are the changes needed?
Resolving a graph with more flows than the parallelism (10) kept one CPU core busy at 100% doing no useful work for the whole resolution, which is wasteful and shows up as unexplained driver CPU.
Does this PR introduce any user-facing change?
No.
How was this patch tested?
Two new cases in
ConnectValidPipelineSuitecover the regime this PR changes - more flows thanparallelism(10), so the slots fill and the loop reaches the blockingtake()branch that replaces the busy-wait. The small graphs in the existing suites never get there.resolution terminates and resolves all flows when flow count exceeds parallelism- 25 independent flows.resolution re-queues retryable flows under load when consumers exceed parallelism- 20 consumers registered before their sourcesrc, so the first batch throwsTransformNodeRetryableException, parks as dependents ofsrc, and is re-queued oncesrcresolves; this exercises the retryable re-queue path together with the blocking branch.Both assert only the outcome (every flow resolves and the call returns), so they are deterministic and have no timing dependence - a regression that deadlocked would hang until the suite times out. Asserting the absence of a busy-wait directly is not included, since that requires CPU-time or timing measurements that are flaky in CI.
Existing graph-resolution suites (
ConnectValidPipelineSuite,ConnectInvalidPipelineSuite,SqlPipelineSuite,TriggeredGraphExecutionSuite,MaterializeTablesSuite) still pass; the change only affects how the loop waits, not what it resolves.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 4.8)