Skip to content

Add echo-ws-limited: WebSocket echo with short-lived connections#1030

Merged
MDA2AV merged 1 commit into
mainfrom
ws-limited-conn
Jul 24, 2026
Merged

Add echo-ws-limited: WebSocket echo with short-lived connections#1030
MDA2AV merged 1 commit into
mainfrom
ws-limited-conn

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Jul 24, 2026

Copy link
Copy Markdown
Owner

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-conn profile.

[echo-ws-limited]="1|10|0-31,64-95|512,4096|ws-echo"

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 -r caps frames exactly as 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 — that's the one line added. The other two WebSocket profiles pass 0 and are byte-for-byte unaffected:

echo-ws           …/ws --ws -c 512 -t 64 -d 5s -p 1
echo-ws-pipeline  …/ws --ws -c 512 -t 64 -d 5s -p 16
echo-ws-limited   …/ws --ws -c 512 -t 64 -d 5s -p 1 -r 10

Measured, not assumed

Against a reference RFC 6455 echo server, 8 connections over 3s:

responses reconnects server handshakes
no -r 335,916 8
-r 10 300,662 30,066 24,210

30,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 that Sec-WebSocket-Accept is computed per connection — a server returning a precomputed value answers the first handshake and fails every one after it.

Wiring

PROFILE_ORDER, the CATALOG entry and PROFILE_DOC mapping, the isolated-test list in benchmark.sh, 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 instead of echo-ws alone, so subscribing to this one is enough to have /ws validated.

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 under test-profiles/ws/echo-limited/ with a card on the WebSocket index; verified all three pages render, type-rules widget included.

🤖 Generated with Claude Code

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
MDA2AV merged commit 3ab01a9 into main Jul 24, 2026
3 checks passed
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add limited connection style for websockets

1 participant