[CELEBORN-2459] Fix race causing DataPusher to accept data during termination and drop it - #3844
[CELEBORN-2459] Fix race causing DataPusher to accept data during termination and drop it#3844rjvkr2021 wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Fixes a race in DataPusher’s shutdown path where tasks could be admitted during termination and then dropped, by introducing an explicit lifecycle state machine and admission barrier.
Changes:
- Add
RUNNING -> CLOSING -> TERMINATEDlifecycle gating foraddTask()vswaitOnTermination(). - Track and drain in-flight producers before allowing termination to proceed.
- Add unit tests covering admission rejection while closing and draining of pre-admitted work.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| client/src/main/java/org/apache/celeborn/client/write/DataPusher.java | Introduces lifecycle state + producer-drain coordination to prevent task admission during termination. |
| client/src/test/java/org/apache/celeborn/client/write/DataPushQueueSuiteJ.java | Adds unit tests that exercise the new closing/termination behavior under concurrency. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| lifecycleLock.lockInterruptibly(); | ||
| try { | ||
| if (lifecycleState != LifecycleState.RUNNING) { | ||
| throw new IOException("DataPusher is closing or terminated"); |
There was a problem hiding this comment.
updated the exception message accordingly
|
@cxzl25 can you please review the pr? i have addressed all the comments left by copilot. |
|
@cxzl25 just a gentle reminder. |
|
|
||
| dataPusher.addTask(0, new byte[1], 1); | ||
| AtomicReference<Throwable> terminationFailure = new AtomicReference<>(); | ||
| Thread terminationThread = |
There was a problem hiding this comment.
Is this a real production environment case?
There was a problem hiding this comment.
I don’t know. Actually, I was investigating something else when I discovered this race.
What changes were proposed in this pull request?
This PR fixes a race causing
DataPusherto accept data during termination and drop it.Why are the changes needed?
Background
When a task wants to push shuffle data to a Celeborn worker, it does not push the
data directly. Instead, the data is copied into a reusable buffer and enqueued
for asynchronous processing.
The producer returns successfully after the buffer is accepted by the queue. A
background thread later dequeues the buffer, pushes its data to the worker, and
returns the buffer to the reusable buffer pool.
Race
The race occurs when the background thread is being terminated. In the original
implementation, termination waits for the reusable buffer pool (
idleQueue)to become full and then sets
terminated=true.The check and the state change are not an atomic admission barrier. After the
buffer-pool check succeeds but before
terminatedis set, a producer canborrow a reusable buffer, copy data into it, enqueue it successfully, and
return from
addTask().The background thread can then observe
terminated, stop without processingthe newly enqueued buffer, and termination can clear the queue.
Impact
The producer has reported success even though the data was never pushed to the
worker. This causes silent data loss.
Fix
Task admission and background thread termination must be ordered through an explicit lifecycle:
When termination begins, the pusher transitions to
CLOSINGbefore waiting forexisting work to finish. New calls to
addTask()are rejected inCLOSING,while producers admitted in
RUNNINGare allowed to complete. After alladmitted producers and queued tasks have been drained, the pusher transitions
to
TERMINATED.This guarantees that every successful
addTask()corresponds to work admittedbefore termination and therefore handled before termination completes.
Does this PR resolve a correctness bug?
Does this PR introduce any user-facing change?
No
How was this patch tested?
Added following unit tests: