Repository navigation
[Bugfix][KV Offload][P2P] Never block in ZmqConnection.send() - #63708
Merged
Merged
Conversation
ControlConnection.send() is documented as non-blocking, but the ZMQ implementation called dealer.send() without NOBLOCK, so it blocked once the queue toward a peer that stopped reading was full. Callers hold the tiering manager lock, so a blocked send also stalled the scheduler. Send with NOBLOCK at an explicit SNDHWM and raise ConnectionError when the queue is full; P2PSession._do_send already marks the session dead on a send error. Bound DEALER linger as well: with the default (-1), a message queued for an unreachable peer made Context.destroy() hang. Part of vllm-project#63602 (gap 1). Signed-off-by: Liran Schour <lirans@il.ibm.com>
orozery
approved these changes
Oct 26, 2026
Contributor
|
/ci run |
|
✅ @liranschour, CI is now available for this PR.
|
|
✅ Triggered Buildkite CI #97291 for commit ℹ️ This PR is 1 commit behind upstream |
6 tasks done
Micador
pushed a commit
to Micador/vllm
that referenced
this pull request
Oct 27, 2026
…roject#63708) Signed-off-by: Liran Schour <lirans@il.ibm.com> Co-authored-by: vllm-agent <claw@inferact.ai> Signed-off-by: Christopher Mackenzie <christopher.mackenzie.software@gmail.com>
Micador
pushed a commit
to Micador/vllm
that referenced
this pull request
Oct 27, 2026
…roject#63708) Signed-off-by: Liran Schour <lirans@il.ibm.com> Co-authored-by: vllm-agent <claw@inferact.ai> Signed-off-by: Christopher Mackenzie <christopher.mackenzie.software@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Part of #63602 (gap 1).
ControlConnection.send()is documented as non-blocking (control/base.py: "ControlConnection.send() must not block"), butZmqConnection.send()calleddealer.send(data)withoutNOBLOCK, at libzmq's defaultSNDHWMand with noSNDTIMEO. Once the queue toward a peer that stops reading fills, the send blocks. Every caller holds the tiering manager lock: the tier-poll thread holds it for a whole round, andOffloadingConnectorScheduler._acquire_step()waits on it with no timeout. A blocked send therefore stalls healthy-peer servicing and the engine's next scheduler step.This PR:
zmq.NOBLOCKat an explicitSNDHWM(1000, unchanged from the libzmq default but no longer implicit) and raisesConnectionErrorwhen the queue is full.P2PSession._do_sendalready marks the connection dead on any send error, so a stalled peer is reaped and its pending work fails over to local prefill. [Bugfix][KV Offload][P2P] Mark the session dead when its ConnectAck send fails #63664 routes the ConnectAck send through the same path.LINGERto 500 ms. While writing the test I found that, with the default (-1), closing a DEALER that still has messages queued for an unreachable peer makesContext.destroy(linger=0)hang forever. The context-level linger only applies to sockets that are still open. Before this change that path was rare, since a full queue blocked the sender first; now that queued-then-dead is the normal failure path, it has to be bounded.ZMQ_IMMEDIATEat its default of 0. WithIMMEDIATE=1, aNOBLOCKsend fails before the TCP connect completes, which would break theConnectMsgsent right afterconnect().On the issue's policy question (fail now vs. retry on later polls): this fails now. Control messages are small, so a full queue means the peer went 1000 messages without reading while its TCP connection stayed up. That isn't short-lived backpressure, and a retry outbox would need its own bound plus ordering against the pre-ack queue.
Not a duplicate: the only open PRs referencing #63602 are #63628 (gap 4) and #63664 (gap 5). #63207 changes the DEALER monitor teardown, not
send().Test plan
New
TestZmqTransportConnectivity::test_send_raises_instead_of_blocking_when_queue_full: a real DEALER connects to a port with no listener, with_SNDHWMpatched to 2.send()must raiseConnectionErrorwithin 1 s, andtransport.close()must return.Test results
send(): hangs inzmq/sugar/socket.py:send, called fromZmqConnection.send(faulthandler dump at 20 s).send()raises, then the test hangs inZmqTransport.close()→Context.destroy().tests/v1/kv_offload/tiering/p2p): 312 passed, CPU-only (pyzmq 27.1.0, libzmq 4.3.5).No model evaluation: this changes only control-plane failure handling, not outputs.
AI assistance (Claude Code) was used to write this change. I reviewed every line and ran the tests above.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Need help on this PR? Tag
@codesmith-botwith what you need.