Skip to content

[CELEBORN-2501] Fix DataPushQueue dropping tasks when there is no partition location map - #3895

Closed
ForVic wants to merge 2 commits into
apache:mainfrom
ForVic:ForVic/o5nq5
Closed

ForVic wants to merge 2 commits into
apache:mainfrom
ForVic:ForVic/o5nq5

Conversation

@ForVic

@ForVic ForVic commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Minor bug fix, ran into it while running a micro benchmark.
When client.getPartitionLocation returns null, DataPushQueue.takePushTasks takes every queued task with:

tasks.addAll(workingQueue);
workingQueue.clear();

The writer thread keeps adding to workingQueue while this runs. If it adds a task after addAll has copied the queue and before clear runs, clear removes that task even though it was never copied into tasks. The task is never pushed, and its PushTask buffer never goes back to DataPusher's idle queue.

This PR replaces the two calls with workingQueue.drainTo(tasks), which removes only the tasks it returns. The branch behaved this way before CELEBORN-1218 (#2220), which removed each task through iterator.remove().

Why are the changes needed?

Each lost task loses a pushed batch and one buffer from the idle pool. Once every buffer is lost, the writer blocks forever in DataPusher.addTask waiting for an idle buffer, and the push thread sleeps on an empty working queue. If fewer are lost, waitOnTermination waits forever for the idle queue to fill up again.

ShuffleClientImpl.getPartitionLocation never returns null: it returns the registered map or throws. So production writers do not take this branch. DummyShuffleClient returns null when no reduce partition map was initialized, so tests and benchmarks built on it can hang.

Does this PR resolve a correctness bug?

  • Yes

Does this PR introduce any user-facing change?

  • Yes

How was this patch tested?

  • New DataPushQueueSuiteJ#testTaskAddedWhileTakingAllTasksIsNotLost. A package-private constructor lets the test pass in its own working queue. The queue adds a second task right after takePushTasks reads the queued tasks, which is exactly the gap between the old addAll and clear. The test checks that takePushTasks returns the first task and that the second one is still queued.
    • Without the fix it failed 3 runs out of 3, because the second task was cleared.
    • With the fix it passes.
  • Before switching to this test, a 200,000-task stress test through DataPusher with no partition map hung the old code after 90,829 pushes, with the writer parked in addTask and the push thread sleeping in takePushTasks.
  • Existing DataPushQueueSuiteJ.

@ForVic ForVic changed the title [CELEBORN-XXXX] Fix DataPushQueue dropping tasks when there is no partition location map [CELEBORN-2501] Fix DataPushQueue dropping tasks when there is no partition location map Oct 22, 2026
@ForVic
ForVic marked this pull request as ready for review October 22, 2026 22:33
@ForVic

ForVic commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

cc @zaynt4606 or @SteNicholas if you get a chance to review sometime

@zaynt4606 zaynt4606 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, with one non-blocking test nit below.

writer.submit(
() -> {
byte[] b = new byte[4];
for (int i = 0; i < numTasks; i++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (non-blocking): Could we replace the 200,000-task stress loop with a deterministic regression test that explicitly controls the producer/consumer interleaving, for example with latches? The current test relies on scheduling to expose the race. Ideally, the test should force an enqueue between the old snapshot and clear operations and verify that the task is preserved, failing reliably with the old implementation and passing with drainTo.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good callout, improved the test

…tition location map

takePushTasks copied the working queue with addAll and then called clear.
A task the writer added between the two calls was cleared without being
returned, so it was never pushed and its buffer never went back to the
idle queue. Use drainTo, which removes only the tasks it returns.

@zaynt4606 zaynt4606 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zaynt4606 zaynt4606 closed this in d750edd Oct 25, 2026
zaynt4606 pushed a commit that referenced this pull request Oct 25, 2026
…tition location map

### What changes were proposed in this pull request?
Minor bug fix, ran into it while running a micro benchmark.
When `client.getPartitionLocation` returns `null`, `DataPushQueue.takePushTasks` takes every queued task with:

```java
tasks.addAll(workingQueue);
workingQueue.clear();
```

The writer thread keeps adding to `workingQueue` while this runs. If it adds a task after `addAll` has copied the queue and before `clear` runs, `clear` removes that task even though it was never copied into `tasks`. The task is never pushed, and its `PushTask` buffer never goes back to `DataPusher`'s idle queue.

This PR replaces the two calls with `workingQueue.drainTo(tasks)`, which removes only the tasks it returns. The branch behaved this way before CELEBORN-1218 (#2220), which removed each task through `iterator.remove()`.

### Why are the changes needed?

Each lost task loses a pushed batch and one buffer from the idle pool. Once every buffer is lost, the writer blocks forever in `DataPusher.addTask` waiting for an idle buffer, and the push thread sleeps on an empty working queue. If fewer are lost, `waitOnTermination` waits forever for the idle queue to fill up again.

`ShuffleClientImpl.getPartitionLocation` never returns `null`: it returns the registered map or throws. So production writers do not take this branch. `DummyShuffleClient` returns `null` when no reduce partition map was initialized, so tests and benchmarks built on it can hang.

### Does this PR resolve a correctness bug?

- [ ] Yes

### Does this PR introduce _any_ user-facing change?

- [ ] Yes

### How was this patch tested?

- New `DataPushQueueSuiteJ#testTaskAddedWhileTakingAllTasksIsNotLost`. A package-private constructor lets the test pass in its own working queue. The queue adds a second task right after `takePushTasks` reads the queued tasks, which is exactly the gap between the old `addAll` and `clear`. The test checks that `takePushTasks` returns the first task and that the second one is still queued.
  - Without the fix it failed 3 runs out of 3, because the second task was cleared.
  - With the fix it passes.
- Before switching to this test, a 200,000-task stress test through `DataPusher` with no partition map hung the old code after 90,829 pushes, with the writer parked in `addTask` and the push thread sleeping in `takePushTasks`.
- Existing `DataPushQueueSuiteJ`.

Closes #3895 from ForVic/ForVic/o5nq5.

Authored-by: ForVic <victor.lakers0@gmail.com>
Signed-off-by: zhengtao <shuaizhentao.szt@alibaba-inc.com>

AI-Contributed/Feature: 0/32
AI-Contributed/UT: 0/56

(cherry picked from commit d750edd)
Signed-off-by: zhengtao <shuaizhentao.szt@alibaba-inc.com>
@zaynt4606

Copy link
Copy Markdown
Contributor

Merge Summary:

Posted by merge_pr.py

@zaynt4606

Copy link
Copy Markdown
Contributor

Thanks @ForVic for the contribution!
Merged to main(v1.0.0) (d750edde) and branch-0.7(v0.7.1) (936ebd0f).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants