mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #3066] [Bug]: Silent data corruption risk on Linux 5.15 when using io_uring offload due to broken short-read validation logic #789
Labels
No labels
ascend
auto-closed
bug
bug
CI/Build
Common
documentation
enhancement
feature request
good first issue
help wanted
high priority
MetaX
nightly-failure
pull-request
PyTorch Backend
question
RFC
Roadmap
stale
Store
Tests
Transfer Engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/Mooncake#789
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @waliwali777 on GitHub (Jul 23, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/3066
Originally assigned to: @LujhCoconut on GitHub.
Bug Report
Environment
5.15.0-140-generic(Ubuntu 22.04 Jammy LTS)MOONCAKE_OFFLOAD_USE_URING=trueSymptom
Non-deterministic data precision / CRC mismatch errors occur randomly during bucket load operations. The issue cannot be reproduced stably, and no explicit IO error code is returned to upper-layer callers, making silent corruption invisible to users.
Trigger Condition
The corruption appears when io_uring short-read events happen under Linux 5.15:
Root Cause Suspect & Code Reference
File:
mooncake-store/src/uring_file.cpp,mooncake-store/src/storage_backend.cpp,Line:
github.com/kvcache-ai/Mooncake@592f117822/mooncake-store/src/uring_file.cpp (L274),github.com/kvcache-ai/Mooncake@592f117822/mooncake-store/src/storage_backend.cpp (L1952)There is a confusing assignment logic after invoking
uring_file->read_aligned():The variable
read_reswhich stores the actual IO byte count returned by io_uring is overwritten by the expected full slice size.All subsequent short-read validation logic based on
read_reswill always evaluate tofalse:ErrorCode::FILE_READ_FAILwill be propagated to upstreamQuestions
read_res = plan.dest_slice.sizeat line 1952? Why override the real IO read result returned byread_aligned()?Before submitting...
@github-actions[bot] commented on GitHub (Jul 23, 2026):
Thanks for opening this issue, @waliwali777!
51405922A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@waliwali777 commented on GitHub (Jul 23, 2026):
@zhangzuo21 、@ykwd
https://github.com/kvcache-ai/Mooncake/pull/1500
hi~能帮忙看一下这个 issue 吗?感谢!
@LujhCoconut commented on GitHub (Jul 23, 2026):
Thanks for the detailed report. Confirmed this is a real issue.
I actually addressed this in PR #2502, but that PR bundled additional optimizations that introduced a design trade-off (replacing zero-copy with a temp buffer + memcpy), which needs further consideration before merging. I'll extract just the two core fixes — stale CQE filtering in
collect()andexpected_totalvalidation in BatchLoad — from #2502 into a separate, focused PR to resolve this issue.For context, my fix in #2502 worked at two layers:
At the io_uring layer (
uring_file.cpp): Tagged each batch submission with a monotonicop_idviasqe->user_data, and rewrotecollect()to useio_uring_wait_cqein a loop — filtering out CQEs whoseuser_datadidn't match. This prevents stale CQEs left by a previously failedbatch_readfrom contaminating the current operation's byte count.At the storage backend layer (
storage_backend.cpp): Afterbatch_readreturns, comparebatch_res.value()against a pre-computedexpected_total(sum of allReadDesc.len). If they don't match, fail withFILE_READ_FAILinstead of silently proceeding with truncated data.These two guard each other: the
expected_totalcheck is the safety net for short reads; theuser_datafilter ensures that safety net isn't bypassed by stale CQE noise making the numbers coincidentally add up.Happy to discuss further.
@waliwali777 commented on GitHub (Jul 23, 2026):
Thanks a lot for your response and the corresponding fixes.
Besides short-read induced silent data corruption, we are also facing identical invisible data inconsistency issues caused by abnormal over-length reads in our test environment.
We have observed a counterintuitive behavior occasionally: the value of
cqe->res(actual bytes read from the kernel) can be larger than the chunk length stored incqe->user_data. We are confused about what causes this scenario. Could you clarify whether the current fixes will also resolve this correctness risk?@LujhCoconut commented on GitHub (Jul 23, 2026):
@waliwali777
Yes, the same fixes also resolve the over-length-read case.
The observed
cqe->res > current chunk sizeis also caused by stale CQEs, not by a kernel bug. Clarification: in the fixcqe->user_datastores a per-operationop_id, not the chunk size.Typical sequence:
submit_rw/batch_readsubmits N SQEs and returns early becausecollect()reports an I/O error. Note thatcollect()itself consumes the expected CQEs before returning; it is the caller that aborts, leaving in-flight SQEs from the failed operation.op_id.collect()usedio_uring_for_each_cqewithout filtering, so it picked up the stale CQE whose original request was much larger (e.g., 1 MB).cqe->res(up to 1 MB) was added to the current operation'stotal, giving the illusion that a single current chunk returned more bytes than requested. The original 1 MB read completion may also have overflowed a buffer that has since been freed or reused.Fix #2 stamps every SQE with a monotonic
op_idandcollect()discards any CQE whoseuser_datadoes not match:So stale CQEs can no longer inflate the current operation's byte count, which eliminates both the short-read and the over-length-read silent corruptions.
Combined with Fix #1 (min_required validation in storage_backend.cpp), the current operation only sees completions it actually submitted and must satisfy the real length requirement.