[BUG]: SGLang disaggregated Prefill continues running after client disconnect before the first token
Describe the Bug
In an SGLang Prefill/Decode disaggregated deployment, disconnecting the client before the first generated token does not promptly cancel the selected Prefill request.
The Dynamo Frontend detects the closed connection and issues cancellation, but the Prefill worker continues processing the remaining prompt chunks. New requests can remain queued behind work whose client has already disconnected.
Decode-phase cancellation works after generation has started. The failure is specific to cancellation during Prefill, before the first SGLang engine output is available.
Steps to Reproduce
- Deploy Dynamo with SGLang in a disaggregated topology containing at least one Prefill worker and one Decode worker. Use NIXL as the disaggregation transfer backend.
- Send a cold streaming
/v1/completionsrequest with a long prompt, for example approximately 128K–256K input tokens, and setmax_tokens=1. - Close the client connection after approximately one second, before receiving the first response.
- Immediately send a short generation request.
- Correlate the Frontend, Prefill, and Decode logs using the request ID.
- Observe the Prefill scheduler state and the latency of the following short request.
The issue is easier to reproduce when the Prefill worker has limited concurrency, because the cancelled long request blocks the following short request.
Expected Behavior
When the client disconnects:
- Cancellation should propagate to the selected Prefill request.
- SGLang should receive an abort for the exact registered request ID.
- Chunked Prefill should stop at the next safe cancellation boundary instead of processing the entire prompt.
- Decode should still be allowed to establish and clean up an in-flight KV receiver when required by the PD protocol.
- NIXL/KV-transfer resources should be drained and released safely.
- A following short request should not wait for the cancelled long Prefill to complete.
Actual Behavior
The Frontend detects the disconnect promptly, but the Prefill worker receives no cancellation before its first engine output.
The cancelled request continues processing all remaining Prefill chunks. In one reproducible case, an immediately following short request was delayed by approximately 40–45 seconds and completed only when the cancelled long Prefill finished.
No worker crash or restart is required to reproduce the issue. This appears to be a request cancellation propagation and lifecycle problem rather than a worker-health problem.
A Decode-phase control test succeeds: after Decode starts producing output, disconnecting the client causes SGLang abort_request to
run and scheduler state returns to idle promptly.
Environment
- Dynamo: development branch based on commit
dc8cead53730a4a05bc91426f39f84916e7c801d - Package version:
1.5.0 - Backend: SGLang
0.5.17 - KV transfer: NIXL
1.3.2 - Deployment: Kubernetes, disaggregated Prefill/Decode
- Topology used for reproduction: 1 Prefill worker and 1 Decode worker
- GPU architecture: NVIDIA H20
- Operating system: Linux
- Python: 3.10
Additional Context
Code-path investigation indicates two lifecycle gaps.
First, PrefillRouter::generate creates the remote Prefill request with Context::with_id_and_metadata. This creates a new context
controller. Although the Prefill context uses the same textual request ID, it does not share cancellation state with the original HTTP
request context unless the two controllers are explicitly linked.
As a result, the Frontend can successfully kill the original request context while the remote Prefill context remains live.
Second, the SGLang cancellation monitor currently learns the engine request ID from the first response. Long chunked Prefill does not
produce that response until Prefill is effectively complete, so the cancellation monitor cannot call abort_request early enough.
SGLang registers the request ID when its lazy generation iterator is first advanced. Cancellation therefore needs to be observed
before the first output and retained across the request-registration race. Calling abort_request before registration is insufficient
because an unknown request ID may be ignored.
The expected lifecycle is:
- Propagate cancellation from the original request context to the selected Prefill context.
- Determine the effective SGLang request ID before the first output.
- Advance the lazy SGLang iterator and wait for request registration.
- If cancellation arrives before registration, retain it and dispatch exactly one abort after registration.
- Continue draining any accepted Prefill/Decode KV-transfer lifecycle instead of dropping the stream immediately.
- Await tracked Prefill cleanup before shutting down the SGLang engine.
This is related to #9388, which discusses SGLang bootstrap registration ordering, but the issue here is specifically client-disconnect cancellation propagation during an active Prefill.
It is also related to #3508 regarding Prefill draining during graceful worker shutdown, but this issue occurs during normal request processing without a worker shutdown.
Screenshots
No response
Source: ai-dynamo/dynamo