Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(rdma): restore task.request association in submitTransfer#2610

Merged
alogfans merged 1 commit into
kvcache-ai:mainfrom
he-yufeng:fix/rdma-submittransfer-task-request
Jun 25, 2026
Merged

fix(rdma): restore task.request association in submitTransfer#2610
alogfans merged 1 commit into
kvcache-ai:mainfrom
he-yufeng:fix/rdma-submittransfer-task-request

Conversation

@he-yufeng

@he-yufeng he-yufeng commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Description

RdmaTransport::submitTransfer(BatchID, entries) stopped associating each task with its originating request, so the tasks it hands to submitTransferTask() carry a null request pointer.

In #772 this overload was simplified to delegate slice construction to submitTransferTask() instead of building slices inline. The inline loop used to read each entries[i] directly; the rewrite removed that loop but did not add the task.request = &entries[i] assignment that submitTransferTask() now depends on:

// submitTransferTask(), per task:
assert(task.request);
auto &request = *task.request;   // null deref when request was never set

The leftover task_id variable in the current code (computed, then only used by resize) is what remains of that deleted per-entry loop.

This overload is not on the normal engine path — MultiTransport::submitTransfer sets task.request itself and calls submitTransferTask directly. It is reached through HeterogeneousRdmaTransport::submitTransfer, which forwards both the CPU-source fast path and the staged-host path to transport_->submitTransfer(batch_id, entries) (where transport_ is a std::unique_ptr<RdmaTransport>). On that path every submitted task has request == nullptr: an assertion failure in debug builds, and a null-pointer dereference (request.length, request.source, …) in release builds.

The fix repopulates batch_id and request for each newly created task before delegating, mirroring what MultiTransport::submitTransfer does (including the USE_ASCEND_HETEROGENEOUS const handling, since TransferTask::request is non-const in that build) and what TcpTransport::submitTransfer does inline. As a side effect it also stops re-submitting pre-existing tasks in the batch, since it now iterates the new entries rather than the whole task_list.

Module

  • Transfer Engine (mooncake-transfer-engine)

Type of Change

  • Bug fix

How Has This Been Tested?

I have not been able to exercise this path at runtime: the only caller is HeterogeneousRdmaTransport, which is built under USE_ASCEND_HETEROGENEOUS and needs Ascend NPU hardware, and RdmaTransport itself needs an RDMA-capable NIC to instantiate. There is no hardware-free unit harness for it in the tree.

So this is verified by code inspection rather than execution:

  • the regression is visible in the [TE][RDMA Transport]: Simplify Transfer Submission Logic #772 diff (the per-entry request consumption was removed without a replacement assignment);
  • submitTransferTask() unconditionally requires task.request;
  • the corrected loop matches the proven MultiTransport::submitTransfer / TcpTransport::submitTransfer siblings;
  • the change compiles in the standard (non-Ascend) build that CI covers.

I'd appreciate a check from someone with the Ascend heterogeneous setup to confirm the runtime path end to end.

Test results:

  • Unit tests pass

RdmaTransport::submitTransfer(BatchID, entries) was simplified in kvcache-ai#772 to
delegate slice construction to submitTransferTask(), but the rewrite dropped
the per-entry assignment of task.request. submitTransferTask() then runs
`assert(task.request); auto &request = *task.request;`, so every task it
receives from this entry point carries a null request pointer.

The overload is reached through HeterogeneousRdmaTransport::submitTransfer,
which forwards CPU-source and staged-host batches straight to
transport_->submitTransfer(batch_id, entries). With the missing assignment
that dereferences a null pointer (assert failure in debug, UB/segfault in
release).

Repopulate batch_id and request for each newly created task before
delegating, matching how MultiTransport::submitTransfer and TcpTransport
associate each task with its originating request.

Signed-off-by: Yufeng He <[email protected]>

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request modifies submitTransfer in rdma_transport.cpp to iterate over the input entries and populate TransferTask requests and batch IDs. Feedback highlights a critical memory safety issue where resizing batch_desc.task_list can trigger reallocation and cause a double-free or use-after-free due to the lack of a custom move constructor in TransferTask. Additionally, there is a risk of undefined behavior from casting away constness on entries using const_cast.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

std::vector<TransferTask *> task_list;
for (auto &task : batch_desc.task_list) task_list.push_back(&task);
for (auto &request : entries) {
auto &task = batch_desc.task_list[task_id];

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.

critical

Critical Memory Safety Issue: Potential Double-Free / Use-After-Free of Slices

batch_desc.task_list is a std::vector<TransferTask>. When batch_desc.task_list.resize() is called, if the new size exceeds the vector's current capacity, the vector will reallocate its internal storage.

During reallocation, the existing TransferTask elements are copied or moved to the new memory location, and the old elements are destroyed.

However, TransferTask (defined in transport.h) has a custom destructor ~TransferTask() that deallocates all slices in its slice_list:

~TransferTask() {
    for (auto &slice : slice_list)
        Transport::getSliceCache().deallocate(slice);
}

But TransferTask does not define a custom move constructor or copy constructor (violating the Rule of Three/Five).

  1. Since a custom destructor is defined, the compiler will not implicitly generate a move constructor.
  2. It will instead fall back to the default copy constructor (which is deprecated but generated), performing a shallow copy of the slice_list pointer vector.
  3. When the old TransferTask elements are destroyed during reallocation, their destructors will run and deallocate all active slices of any pre-existing tasks in the batch.
  4. The new TransferTask elements in the reallocated vector will now hold dangling pointers to these deallocated slices, leading to use-after-free and subsequent double-free when they are eventually destroyed.

Recommendation

To fix this, you should implement a proper noexcept move constructor for TransferTask in transport.h that clears the slice_list of the moved-from object:

TransferTask(TransferTask&& other) noexcept 
    : slice_count(other.slice_count),
      success_slice_count(other.success_slice_count),
      failed_slice_count(other.failed_slice_count),
      transferred_bytes(other.transferred_bytes),
      is_finished(other.is_finished),
      total_bytes(other.total_bytes),
      batch_id(other.batch_id),
      transport_(other.transport_),
      slice_list(std::move(other.slice_list)) {
    other.slice_list.clear();
}

And delete or properly implement the copy constructor/assignment operator to prevent accidental copies.

++task_id;
task.batch_id = batch_id;
#ifdef USE_ASCEND_HETEROGENEOUS
task.request = const_cast<TransferRequest *>(&request);

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.

medium

Undefined Behavior Risk with const_cast

entries is passed as a const std::vector<TransferRequest> &. Casting away constness using const_cast<TransferRequest *>(&request) and subsequently modifying the request (as implied by the comment in transport.h: // need to modify the request's source address...) leads to undefined behavior if the caller passed a truly const object or a temporary.

To ensure safety and maintain const-correctness, the transport APIs should either:

  1. Accept std::vector<TransferRequest> & (non-const) if modification is required.
  2. Avoid modifying the original request object directly, and instead store the modified state (like the CPU source address) in the TransferTask or Slice itself.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 0% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ne/src/transport/rdma_transport/rdma_transport.cpp 0.00% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

@alogfans alogfans left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@alogfans alogfans merged commit 25680f2 into kvcache-ai:main Jun 25, 2026
22 of 24 checks passed
he-yufeng added a commit to he-yufeng/Mooncake that referenced this pull request Jun 25, 2026
EfaTransport::submitTransfer(BatchID, entries) and
UbTransport::submitTransfer(BatchID, entries) resize the batch task list
and then hand every slot to submitTransferTask() without setting
task.request on the newly created tasks. submitTransferTask() runs
`assert(task.request); auto& request = *task.request;`, so each task from
this entry point carries a null request pointer (assert failure in debug,
UB / segfault in release).

This is the same defect kvcache-ai#2610 fixed in RdmaTransport. After that change,
EFA and Kunpeng UB are the only two transports whose submitTransfer
override still omits the per-entry association that MultiTransport,
TcpTransport, and (post-kvcache-ai#2610) RdmaTransport already perform.

Repopulate batch_id and request for each newly added entry before
delegating, and submit only the new tasks instead of re-pushing the whole
list, matching kvcache-ai#2610.

Signed-off-by: Yufeng He <[email protected]>
whn09 pushed a commit that referenced this pull request Jun 26, 2026
…2617)

EfaTransport::submitTransfer(BatchID, entries) and
UbTransport::submitTransfer(BatchID, entries) resize the batch task list
and then hand every slot to submitTransferTask() without setting
task.request on the newly created tasks. submitTransferTask() runs
`assert(task.request); auto& request = *task.request;`, so each task from
this entry point carries a null request pointer (assert failure in debug,
UB / segfault in release).

This is the same defect #2610 fixed in RdmaTransport. After that change,
EFA and Kunpeng UB are the only two transports whose submitTransfer
override still omits the per-entry association that MultiTransport,
TcpTransport, and (post-#2610) RdmaTransport already perform.

Repopulate batch_id and request for each newly added entry before
delegating, and submit only the new tasks instead of re-pushing the whole
list, matching #2610.

Signed-off-by: Yufeng He <[email protected]>
ZhijunLStudio pushed a commit to ZhijunLStudio/Mooncake that referenced this pull request Jul 2, 2026
…e-ai#2610)

RdmaTransport::submitTransfer(BatchID, entries) was simplified in kvcache-ai#772 to
delegate slice construction to submitTransferTask(), but the rewrite dropped
the per-entry assignment of task.request. submitTransferTask() then runs
`assert(task.request); auto &request = *task.request;`, so every task it
receives from this entry point carries a null request pointer.

The overload is reached through HeterogeneousRdmaTransport::submitTransfer,
which forwards CPU-source and staged-host batches straight to
transport_->submitTransfer(batch_id, entries). With the missing assignment
that dereferences a null pointer (assert failure in debug, UB/segfault in
release).

Repopulate batch_id and request for each newly created task before
delegating, matching how MultiTransport::submitTransfer and TcpTransport
associate each task with its originating request.

Signed-off-by: Yufeng He <[email protected]>
ZhijunLStudio pushed a commit to ZhijunLStudio/Mooncake that referenced this pull request Jul 2, 2026
…vcache-ai#2617)

EfaTransport::submitTransfer(BatchID, entries) and
UbTransport::submitTransfer(BatchID, entries) resize the batch task list
and then hand every slot to submitTransferTask() without setting
task.request on the newly created tasks. submitTransferTask() runs
`assert(task.request); auto& request = *task.request;`, so each task from
this entry point carries a null request pointer (assert failure in debug,
UB / segfault in release).

This is the same defect kvcache-ai#2610 fixed in RdmaTransport. After that change,
EFA and Kunpeng UB are the only two transports whose submitTransfer
override still omits the per-entry association that MultiTransport,
TcpTransport, and (post-kvcache-ai#2610) RdmaTransport already perform.

Repopulate batch_id and request for each newly added entry before
delegating, and submit only the new tasks instead of re-pushing the whole
list, matching kvcache-ai#2610.

Signed-off-by: Yufeng He <[email protected]>
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.

3 participants