Repository navigation
Hold the mock server's concurrency slot for the whole stream - #1262
mergify[bot] merged 5 commits into
Conversation
HarshShroff
left a comment
There was a problem hiding this comment.
Ran this at 3061601 on macOS.
-k Concurrencyintests/unit/mock_server/test_server.py: 4 passed. With main'sserver.pyput back,test_max_concurrent_requests_serializes_streamingfails (slowest stream 0.261s against the 0.3s bound) and the disconnect test passes, which is what you'd expect since main never holds the slot for streams.- Since the slot is now released by hand in two places, I wanted to check nothing leaks when clients bail at awkward moments. I started the mock server with
max_concurrent_requestsset, fired streaming requests from separate connections and cancelled each one after a random delay, then sentlimitnon-streaming requests at once with a 5s timeout. If any slot had leaked, at least one of those would hang. Results: limit 4 with 400 abandoned streams (cancel within 0-20ms), and limit 2 with 1,000 abandoned streams twice (cancel within 0-3ms and 0-8ms). Every follow-up finished each time, so I found no leak.
One small thing about the serialization test: main's unlimited run came in at 0.261s, only about 40ms below the 0.3s bound. On a busy CI runner the unlimited case could cross 0.3s, and then the test would pass even without the fix. I didn't time the branch side, but with the slot held the second stream can't start its 0.2s ttft until the first one ends, so it should take roughly 0.4s. A bound around 0.35s would leave more room on both sides.
|
@HarshShroff Thanks for testing this so thoroughly, especially the abandoned-stream runs. That's reassuring. Good catch on the bound. Rather than raising it, I changed the test to compare when the two streams finish instead of how long the slowest one took. Without the fix they finish together, and with it the second one ends about a ttft later, so the gap doesn't depend on how fast the runner is. |
|
Hi @jaredoconnell, sorry to ping you twice in one day! Since you've done a lot of the mock server work, I thought you'd be the right person for this one. It's a small fix so that |
|
Received a single approval. Will automatically queue for merge once two maintainers approve, all change requests are resolved, and DCO passes. |
|
Queued — the merge queue status continues in this comment ↓. |
dbutenhof
left a comment
There was a problem hiding this comment.
This looks good, at a layer of Sanic I hadn't looked at before. The only concern might be that streaming_fn is technically marked "deprecated" -- no idea whether they'll ever actually remove it, but in any case there's no point in expanding this PR to cover that much more extensive refactoring.
Merge Queue Status
This pull request spent 7 minutes 54 seconds in the queue, including 7 minutes 11 seconds running CI. ReasonThe merge conditions cannot be satisfied due to failing checks
Failing checks: HintYou may have to fix your CI before adding the pull request to the queue again. Waiting for
All conditions
Requeued — the merge queue status continues in this comment ↓. |
jaredoconnell
left a comment
There was a problem hiding this comment.
I tested it and it appears to behave as it should.
|
It appears that needs to be updated following this PR: https://github.com/vllm-project/guidellm/pull/1282/changes#diff-b9b431edd343d7d19efc5e36bde8365c1e357a5679a84d80c7c1733343013e9c Main switched to a new http library. |
max_concurrent_requests is meant to cap in-flight generation requests, but for streaming requests it didn't cap anything. The semaphore was only held while the handler ran, and a streaming handler returns its ResponseStream right away. Sanic runs the actual generation afterwards, so the slot was already free by the time the first token went out. Now the slot is released when the stream finishes instead, including when the client disconnects partway through. Non-streaming requests behave as before. Assisted-by: Claude Code claude-opus-5-5 Signed-off-by: MinJun Choi <mj04300017@gmail.com>
The existing test only covered non-streaming requests, which is how the streaming gap went unnoticed. Add one test that overlapping streams run one at a time, and one that a stream the client abandons still frees its slot, so the next request doesn't hang. Assisted-by: Claude Code claude-opus-5-5 Signed-off-by: MinJun Choi <mj04300017@gmail.com>
The test passed when the slower of two overlapping streams took at least 0.3s. Without the fix the streams run together and the slower one took about 0.26s, so a busy CI runner could push it past the bound and let the test pass on a broken server. Check the gap between the two finish times instead. Run together, they finish within about a millisecond of each other; serialized, the second finishes about a ttft later. Over ten runs each, the gap was 0.000-0.001s without the fix and 0.205-0.220s with it, and a slower runner delays both streams without closing the gap. Assisted-by: Claude Code claude-opus-5-5 Signed-off-by: MinJun Choi <mj04300017@gmail.com>
main moved the mock server tests from httpx to httpx2 in vllm-project#1282, so the two new tests no longer resolved httpx.AsyncClient. Assisted-by: Claude Code claude-opus-5-5 Signed-off-by: MinJun Choi <mj04300017@gmail.com>
75e83c9
f51dd12 to
75e83c9
Compare
|
Thanks for catching that, @jaredoconnell! I rebased onto main and switched the two new tests to The push dismissed the earlier approvals, so sorry for the extra round. The only change since the reviews is the |
|
Received a single approval. Will automatically queue for merge once two maintainers approve, all change requests are resolved, and DCO passes. |
Merge Queue Status
This pull request spent 32 seconds in the queue, with no time running CI. ReasonThe pull request conflicts with the base branch Automatic conflict resolution can merge a conflict like this one instead of stopping on it, but only in a queue using the HintYou should update or rebase your pull request. If you want to requeue this pull request, you can post a Requeued — the merge queue status continues in this comment ↓. |
Signed-off-by: MinJun Choi <mj04300017@gmail.com>
c0c95a1
|
@sjmonson @dbutenhof Sorry for the extra round. The merge queue dropped this because of a conflict with main in |
Merge Queue Status
This pull request spent 4 minutes 15 seconds in the queue, including 3 minutes 58 seconds running CI. Required conditions to merge
|
|
Received a single approval. Will automatically queue for merge once two maintainers approve, all change requests are resolved, and DCO passes. |
Summary
Fixes
max_concurrent_requestsfor streaming requests. Right now the slot is released as soon as the handler returns itsResponseStream, before any tokens are generated, so streaming requests were never limited. This holds the slot until the stream actually finishes.Details
_run_generation, a streaming response'sstreaming_fnis wrapped so the slot is released when the stream ends, including when the client disconnects partway through. Non-streaming requests work the same as before.Test Plan
lint-check,type-check,test-unit(3,658 passed),test-integration(32 passed) andtest-e2e(14 passed) all pass.main, and the disconnect test fails if the slot is only released after a stream completes normally.Related Issues
Use of AI
git log
commit 3cd601e
Author: MinJun Choi mj04300017@gmail.com
Date: Thu Oct 8 13:39:40 2026 +0900
commit 416e962
Author: MinJun Choi mj04300017@gmail.com
Date: Thu Oct 8 13:39:40 2026 +0900
commit 9b3167e
Author: MinJun Choi mj04300017@gmail.com
Date: Fri Oct 9 22:31:23 2026 +0900
commit 75e83c9
Author: MinJun Choi mj04300017@gmail.com
Date: Fri Oct 16 11:11:17 2026 +0900
Assisted-by: Claude Code claude-opus-5-5
Signed-off-by: MinJun Choi mj04300017@gmail.com