mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #2930] [Bug] TcpTransport connection pool: unbounded growth with threshold mass-GC; stale entries to restarted peers are never evicted #760
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#760
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 @d3nb on GitHub (Jul 15, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/2930
Originally assigned to: @alogfans on GitHub.
Environment
mooncake-transfer-engine-cuda13 0.3.11.post1 (PyPI), used as SGLang PD-disaggregation KV transfer backend (
MC_FORCE_TCP=1,MC_TCP_ENABLE_CONNECTION_POOL=true), same-host prefill(TP4) → decode(TP4), 8×B300, container network.Issue 1: pool grows unboundedly, then mass-closes tens of thousands of connections at once
Under sustained load (fixed 10-way concurrency, ~27K-token KV parcels, chunked),
/proc/net/tcpon the sender side shows a sawtooth with ~8-minute period:Two problems: (a) between flushes the pool keeps growing toward the ephemeral-port limit (we measured peaks at 83% of a widened 10240-65000 range — with the default ~28K-port range this would hit
EADDRNOTAVAILlong before the flush); (b) the flush itself dumps 20K+ sockets into TIME_WAIT within seconds. A bounded pool with per-peer connection reuse would presumably hold this at a few hundred connections (we see ~566 ESTABLISHED steady when idle-ish).Issue 2: pooled connections to a dead/restarted peer are never evicted
If one side of the PD pair is restarted alone (its listen ports change), the surviving side's pool keeps the stale entries. Small transfers still succeed (presumably via new connections), but large multi-slice transfers stall silently and permanently — the sender reports no error, the receiver times out (
KVPoll.WaitingForInputtimeout after 300s). It also degrades progressively: after one single-sided peer restart we only measured throughput loss at high concurrency; after a second one, all large transfers hung. Restarting both sides together fully restores normal behavior.Ask
Happy to provide more traces or test patches — this is reproducible on demand in our setup.
@github-actions[bot] commented on GitHub (Jul 15, 2026):
Thanks for opening this issue, @d3nb!
7304667A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@d3nb commented on GitHub (Jul 15, 2026):
Adding the triage details requested by the bot:
Mooncake version:
mooncake-transfer-engine-cuda13 0.3.11.post1(PyPI wheel, as bundled in the SGLang imagelmsysorg/sglang:nightly-dev-cu13-20260713-b94ac87e).Environment:
mooncake_tcp,MC_TCP_ENABLE_CONNECTION_POOL=true); the RDMA path is parked on this build due to a "Local segment descriptor not found" regression on long multi-segment transfers (#2177-shaped), so everything below is pure TcpTransportnet.ipv4.ip_local_port_range = 10240 65000,tcp_tw_reuse=1Repro: SGLang PD disaggregation (1 prefill + 1 decode, DeepSeek-V4 class model, KV transfer via TcpTransport), then any benchmark that ramps concurrency (we use aiperf max-concurrency-under-sla over a ~5000-request trace with ~27K-token mean prompts). Watch
ss -s/ per-state counts on either worker.Actual:
EADDRNOTAVAILinstead (that's what forced the pool on).Expected: bounded pool with idle/LRU eviction and reuse (no sawtooth mass-GC), and eviction of pool entries on connection error so a restarted peer heals without restarting the survivor.
@alogfans commented on GitHub (Jul 16, 2026):
Thanks for the detailed repro and socket-state timeline.
The pool is currently keyed by host/port and does not have a per-peer or global bound. When all pooled sockets for a peer are in use, getConnection() creates another socket and appends it to the peer queue. Under sustained chunked KV transfers, especially with many concurrent slices, that means the number of ESTABLISHED sockets can grow with workload pressure instead of being capped at a small reuse set.
The sawtooth/TIME_WAIT burst is also consistent with the cleanup behavior. Idle cleanup is triggered lazily from getConnection(), and expired idle sockets can be closed in one cleanup pass. So after a burst of transfers completes, many sockets can become idle around the same time, and a later cleanup pass may close a large batch together, producing the TIME_WAIT spike you measured.
For the restart case, socket->is_open() is not a sufficient liveness check for a pooled TCP connection. A socket can still look open locally after the peer process has exited or after a single-sided restart. The current pool also does not track a peer instance/generation, so it cannot reliably distinguish “same host/port, old peer” from “same logical peer after restart”. That can leave stale pooled entries eligible for reuse until a later async read/write path detects the failure, which is especially painful for large multi-slice transfers.
Planned fixes:
Bound the pool
Make cleanup gradual
Evict bad connections reliably
Handle peer restart
As this may incur significant changes of the tcp transport, we will perform these fixes seperately.
@github-actions[bot] commented on GitHub (Oct 14, 2026):
This issue has had no activity for 90 days and will be closed in 7 days if there is no further activity. Please comment or react if it should stay open.
@d3nb commented on GitHub (Oct 18, 2026):
Quick status from our side, since the stale bot is counting down: the fixes that came out of this thread have landed.
v0.3.13/v0.3.13.post1.The measurements in this issue were taken on
0.3.11.post1. We'll re-run the same socket-state timeline (ESTABLISHED growth, TIME_WAIT bursts, single-side restart) on0.3.13.post1and report the result here. If it reproduces nothing, we'll close this ourselves; leaving it open until then.Thanks @alogfans for taking the four planned fixes through.