mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #1986] [Bug]: SIGSEGV in MemcpyWorkerPool::workerThread under TCP transport with concurrent put/get #551
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#551
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 @Dogacel on GitHub (Apr 27, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/1986
Originally assigned to: @fang-tech on GitHub.
Bug Report
Summary
On a TCP-only host,
MC_STORE_MEMCPYauto-enables and creates aMemcpyWorkerPool. Under concurrentput/getfrom a multi-threaded Python client, a worker thread segfaults inside glibc__memcpy_avx512_unaligned_erms. SettingMC_STORE_MEMCPY=0reliably avoids the crash.Regression PR: https://github.com/kvcache-ai/Mooncake/pull/1936
Reproduction
puts host buffers and anothergets them concurrently over TCP.I've used https://github.com/torchspec-project/TorchSpec's Qwen 8B training pipeline.
Crash signature
gdbon a core dump shows the crashing thread:Workaround
This bypasses
MemcpyWorkerPooland the crash disappears completely. Switching transport to RDMA also avoids it (RDMA path doesn't use the pool).Environment
mooncake-transfer-engine0.3.10.post2 (pip)6.8.0-1043-aiext, glibc default, systemlibstdc++.so.6put_from, trainer workers callget_intohost buffers concurrently.MC_STORE_MEMCPYnot set → log shows:auto-detected: TCP-only environment, memcpy enabledBefore submitting...
@fang-tech commented on GitHub (Apr 27, 2026):
I'm working on this problem, plz assign it to me.
@ykwd commented on GitHub (Apr 28, 2026):
Thanks a lot!
@stmatengss commented on GitHub (Apr 29, 2026):
Please let me know if you have any fixes. Thanks.
@fang-tech commented on GitHub (Apr 29, 2026):
Root Cause Analysis
The branch point that leads to the
memcpypath isselectStrategy()inmooncake-store/src/transfer_task.cpp. WhenMC_STORE_MEMCPY=1(automatically set on TCP-only hosts) andisLocalTransfer()returnstrue,selectStrategy()returnsTransferStrategy::LOCAL_MEMCPY.The intended semantics of
isLocalTransferis to determine whetherbuffer_address_can be directly dereferenced in the current process's virtual address space — i.e., whethermemcpy(dest, buffer_address_, size)can be executed safely without going through the network. This condition holds only when the buffer was allocated by the current process itself. However, the current implementation only checks whether the buffer resides on the same host, not whether it belongs to the same process:Fix
Restore
isLocalTransferto compare the fullIP:portinstead of IP only.Question
Historically the logic did perform a full
IP:portcomparison, but PR #1226 changed it to the current IP-only form. Was there a specific consideration behind that change? Understanding the intent would help confirm that this fix does not introduce any unintended regressions.cc @stmatengss @ykwd
@fang-tech commented on GitHub (Apr 29, 2026):
Reproduction
Terminal 1
Terminal 2
put_worker.py
Terminal 3
get_worker.py
Crash Signature
Thread 55 "python3" received signal SIGSEGV, Segmentation fault.
Thread 55 (Thread 0x7fe788ff9640 (LWP 103854) "python3"):
#0 __memmove_evex_unaligned_erms ()
at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:317
#1 mooncake::MemcpyWorkerPool::workerThread ()
from .../site-packages/mooncake/store.so
#2 std::execute_native_thread_routine
#3 start_thread
#4 clone
@fang-tech commented on GitHub (Apr 29, 2026):
And if the above fix looks correct, I'd be happy to submit a PR addressing this, along with relevant unit tests.
@00fish0 commented on GitHub (Apr 30, 2026):
@fang-tech Thanks a lot for the clean reproduction script and the careful root-cause writeup. I tried it on my side and reproduced the segfault almost immediately with your three-terminal setup — with MC_STORE_MEMCPY=1 the get_worker SIGSEGVs on the first read, and with MC_STORE_MEMCPY=0 it stays alive, exactly as you described.
Your analysis on isLocalTransfer() reads correct to me. The IP-only comparison was introduced in #1226, and combined with the per-process unique ip:port from #852's AutoPortBinder, it can no longer distinguish processes on the same host — so the LOCAL_MEMCPY path ends up dereferencing another process's virtual address. Restoring the full ip:port comparison (as you proposed) is the right fix.
For what it's worth, #1995 was opened independently and seems to land on the same conclusion — it restores the full ip:port comparison in isLocalTransfer(), and additionally makes MemcpyWorkerPool::workerThread() GPU-pointer-aware (a related issue when buffers are cudaMalloc'd). So this issue should be resolved once #1995 merges.
Thanks again for the thorough investigation!
@fang-tech commented on GitHub (Apr 30, 2026):
@00fish0 I’m glad this is helpful.
However I found an additional issue. If we simply revert to a strict
IP:portcheck, for examplehandle.transport_endpoint_ == engine_.getLocalIpAndPort(), we may unexpectedly fall back fromLOCAL_MEMCPYtoTRANSFER_ENGINE.In P2P mode, reverting to
IP:portis correct:handle.transport_endpoint_is derived fromengine_.getLocalIpAndPort(), and the endpoint is uniquely bound to the local transfer engine instance.However, in non-P2P mode,
handle.transport_endpoint_comes fromlocal_hostname_, which is a client initialization parameter and may be a logical name such aslocalhost. In that case, comparing it againstengine_.getLocalIpAndPort()will fail by construction, because the latter is the transfer engine's actual runtime endpoint, for example192.168.1.23:34127. Even iflocal_hostname_is an exact IP address, the comparison can still fail because its port may differ from the port assigned by the transfer engine.In practice, this means the strict
IP:portcheck almost always fails in non-P2P deployments, causing the path to fall back fromLOCAL_MEMCPYtoTRANSFER_ENGINE. This may also help explain why#1226switched back to an IP-only comparison.@fang-tech commented on GitHub (Apr 30, 2026):
I noticed that 1995 has already been merged. I will submit a new pull request to fix this issue.
@00fish0 commented on GitHub (May 6, 2026):
Sorry for the late reply! I'll take a look at your PR in the next few days. Thanks for the contribution!