Add echo-ws-limited: WebSocket echo with short-lived connections#1030
Merged
Conversation
Closes #1001. The two existing WebSocket profiles both hold connections open for the whole run, so the upgrade handshake is paid 512 times and amortized over millions of frames — its cost is invisible. This profile retires each connection after 10 messages, so the handshake is paid continuously and shows up in the throughput number. It is the WebSocket counterpart to the HTTP/1.1 limited-conn profile. [echo-ws-limited]="1|10|0-31,64-95|512,4096|ws-echo" No gcannon change was needed. Its per-connection quota check sits above the ws/http branch in worker.c, so -r caps frames the same way it caps requests; once the quota drains the connection is retired, and every replacement connection runs fire_ws_upgrade() again. The adapter simply never passed -r for ws-echo, so this adds that one line — the other two WebSocket profiles pass 0 and are byte-for-byte unaffected: echo-ws ... --ws -c 512 -t 64 -d 5s -p 1 echo-ws-pipeline ... --ws -c 512 -t 64 -d 5s -p 16 echo-ws-limited ... --ws -c 512 -t 64 -d 5s -p 1 -r 10 Measured against a reference RFC 6455 echo server, 8 connections over 3s: no -r 335,916 responses, no reconnects, 8 handshakes -r 10 300,662 responses, 30,066 reconnects, 24,210 handshakes 30,066 x 10 = 300,662: exactly one connection retired per 10 messages, each replacement performing a real handshake, against 8 for the whole run without it. (The server is a single-threaded Python asyncio script, so the 10% throughput delta is a floor, not a prediction for real frameworks.) Servers need no changes: the endpoint, framing and echo contract are identical to echo-ws, so any framework already subscribed to it implements everything this profile requires. The one thing it does newly enforce is that Sec-WebSocket-Accept is computed per connection — a server returning a precomputed value answers the first handshake and fails every one after. Wiring: PROFILE_ORDER, the CATALOG entry and PROFILE_DOC mapping, the isolated-test list in benchmark.sh, the ws-only readiness detection in framework.sh and validate.sh, and the WebSocket validation gate, which now fires for any of the three ws profiles rather than echo-ws alone — so subscribing to this one is enough to have /ws validated. No framework subscribes yet (the issue is tagged next-round), and the generator only emits a profile once results exist, so the board and every composite score are unchanged until a round runs it. Docs added under test-profiles/ws/echo-limited/ with a card on the WebSocket index.
MDA2AV
added a commit
to Kaliumhexacyanoferrat/HttpArena
that referenced
this pull request
Jul 24, 2026
Six merges landed since the last sync: MDA2AV#1030, MDA2AV#1031, MDA2AV#1032, MDA2AV#1033, MDA2AV#1034 and MDA2AV#1036. One real conflict, in the composite blurb, where both sides had changed the same line for different reasons: main (MDA2AV#1032) made the text state which normalization rule is active, so a screenshot can't be ambiguous about whether scores were rescaled to the current filter this branch pointed the "How it works" link at /docs/scoring/composite -score/ instead of the hash route Both are kept: MDA2AV#1032's wording with this branch's real URL. Everything else merged on its own, but the two structural ones were checked rather than assumed: MDA2AV#1034 moved results from 52 shared per-profile arrays to one file per framework. Git applied that to the renamed generator correctly — it still loads site/data/results/*.json and still emits 1941 rows, and compare.sh kept both its per-framework read and the --compare flag from MDA2AV#1031. MDA2AV#1030 added three echo-ws-limited doc pages after this branch's SEO pass, so they were written with seo_title/description already; all 126 pages now carry both. Generator output on the merged tree: 1941 rows, 126 static doc pages, a 126 -page search index and a 127-URL sitemap. All six workflows validate against a loader that rejects duplicate keys, which is what MDA2AV#1036 had to fix.
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.
Closes #1001.
Both existing WebSocket profiles hold their connections open for the entire run, so the upgrade handshake is paid 512 times and amortized over millions of frames — its cost is invisible. This profile retires each connection after 10 messages, so the handshake is paid continuously and lands in the throughput number. It's the WebSocket counterpart to the HTTP/1.1
limited-connprofile.gcannon already supports this
No load-generator change was needed. Its per-connection quota check sits above the ws/http branch in
worker.c, so-rcaps frames exactly as it caps requests; once the quota drains the connection is retired, and every replacement connection runsfire_ws_upgrade()again. The adapter simply never passed-rforws-echo— that's the one line added. The other two WebSocket profiles pass0and are byte-for-byte unaffected:Measured, not assumed
Against a reference RFC 6455 echo server, 8 connections over 3s:
-r-r 1030,066 × 10 = 300,662 — exactly one connection retired per 10 messages, each replacement performing a real handshake, against 8 for the entire run without it.
The issue wondered whether this differs from the regular WebSocket tests: it does, though the reference server here is a single-threaded Python asyncio script, so the ~10% throughput delta is a floor rather than a prediction for real frameworks — the handshake share grows as per-message cost shrinks.
Nothing for framework authors to implement
The endpoint, framing and echo contract are identical to
echo-ws, so any framework already subscribed to it satisfies this profile. The one thing it newly enforces is thatSec-WebSocket-Acceptis computed per connection — a server returning a precomputed value answers the first handshake and fails every one after it.Wiring
PROFILE_ORDER, theCATALOGentry andPROFILE_DOCmapping, the isolated-test list inbenchmark.sh, ws-only readiness detection inframework.shandvalidate.sh, and the WebSocket validation gate — which now fires for any of the three ws profiles instead ofecho-wsalone, so subscribing to this one is enough to have/wsvalidated.Impact until a round runs it: none
No framework subscribes yet (the issue is tagged
next-round), and the generator only emits a profile once results exist — so the board and every composite score are unchanged. Docs added undertest-profiles/ws/echo-limited/with a card on the WebSocket index; verified all three pages render, type-rules widget included.🤖 Generated with Claude Code