Skip to content

vanilla-epoll: serve the echo-ws, h2c and unary-grpc profiles, drop vanilla-ws and vanilla-h2c - #1544

Open
enghitalo wants to merge 3 commits into
MDA2AV:mainfrom
enghitalo:claude/vanilla-epoll-ws
Open

enghitalo wants to merge 3 commits into
MDA2AV:mainfrom
enghitalo:claude/vanilla-epoll-ws

Conversation

@enghitalo

@enghitalo enghitalo commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

vanilla-ws and vanilla-h2c ran the same epoll engine as vanilla-epoll. Each only handed the connection, after an HTTP/1.1 head, to a different protocol machine through vanilla's conn-mode seam (core.queue_takeover + a ConnHandler). This PR moves both into vanilla-epoll and removes the two separate entries.

Changes

WebSocket (echo-ws, echo-ws-pipeline, echo-ws-limited), on :8080

  • GET /ws validates the RFC 6455 upgrade, appends the 101 and takes the connection over.
  • From then on, every readable burst goes to a frame handler. It echoes text and binary, answers ping with pong and completes the close handshake.
  • A pipelined burst is drained in one call and written back in one write.

h2c (baseline-h2c, json-h2c), on a new h2c-only listener on :8082

  • The PRI * connection preface parses as an ordinary request. The handler hands the connection to vanilla's http2 module and appends the server SETTINGS.
  • The RFC 9113 machine surfaces a request only once it is complete.
  • An HTTP/1.1 request on :8082 gets 505, never a 200, so the profile can't measure h1.
  • /json reuses the entry's precomputed prefixes. The body writer is split out of write_json_into.

unary-grpc, on :8080

  • The :8080 handler takes the same PRI * preface.
  • GetSum answers with HEADERS, then the SumReply as DATA, then grpc-status: 0 as trailers.

Removed: the vanilla-ws and vanilla-h2c directories, their site/data/results/*.json files, their container logs and their frameworks.json entries. scripts/validate.sh still mentions vanilla-h2c in a comment; editing it would make CI validate every framework.

vanilla pin: 091c014 → 3dd5a79, in vanilla-epoll and vanilla-io_uring. The 7 commits in between are enghitalo/vanilla#208 plus docs and CI; http2 is unchanged.

  • update ws results #208 computes the WebSocket accept key on the stack.
  • Before it, crypto.sha1 leaked about 820 B per upgrade under -gc none. That is what pushed vanilla-ws to between 0.4 and 1.2 GiB on echo-ws-limited.

vanilla-io_uring:

site/leaderboard/data.js is left to the next benchmark-results commit. Regenerating it from this branch also pulls in unrelated changes from other entries, because the committed copy is behind site/data.

Known, unchanged from vanilla-h2c: each h2c connection allocates its http2 state once and never frees it (-gc none; the engine has no close hook for takeover state).

  • In the local runs below, the container grew to ~2 GiB after the 4096-connection runs.
  • The bench showed the same before: vanilla-h2c reported 1.3 GiB at baseline-h2c 4096 and 1.9 GiB at json-h2c 4096.
  • The harness runs every (profile, connections) pair in a fresh container, so this does not carry into the other profiles.

Testing (local)

WebSocket

  • scripts/validate-ws.py: 7 passed, 0 failed.
  • 16 pipelined 16 KiB frames per connection, and single 512 B frames: no mismatched echoes.
  • 60K upgrade/close cycles: RSS stayed flat at ~16 MiB.

h2c and gRPC — the same checks validate.sh runs, all 17 passed:

  • HTTP/2 on the wire on :8082.
  • /baseline2 with fixed and random inputs, and its content-type.
  • /json content-type, and four (count, m) pairs compared against data/dataset.json.
  • HTTP/1.1 on :8082 gets 505.
  • GetSum with two random pairs: HTTP/2, grpc-status: 0, correct sum.

h2load, with the harness's shapes and nofile limit; 0 failed, all 2xx:

Profile Connections -m req/s
baseline-h2c 256 100 725K
baseline-h2c 4096 100 644K
json-h2c 1024 32 516K
json-h2c 4096 32 457K
unary-grpc 256 100 540K
unary-grpc 1024 100 516K

HTTP/1.1, unchanged: /baseline11 (GET and POST), /json, /pipeline, /static, the 404s, and TLS /json on :8081. /ws on the TLS listener returns 404.

vanilla-io_uring: the image builds and serves /baseline11. The PRI preface and the /ws upgrade on :8080 get 501.

🤖 Generated with Claude Code

vanilla-ws was the same epoll engine with only a /ws route, so it ran as a
separate entry. vanilla-epoll now serves GET /ws on its :8080 listener:
the handler validates the RFC 6455 upgrade, appends the 101 and hands the
connection over (core.queue_takeover). After that, every readable burst
goes to a ConnHandler that walks the frames (echo text/binary, ping -> pong,
close handshake). echo-ws, echo-ws-pipeline and echo-ws-limited move to
vanilla-epoll's meta.json. The vanilla-ws directory, its results file and
its eight container logs are removed.

The vanilla pin moves from 091c014 to 3dd5a79 in vanilla-epoll and
vanilla-io_uring. The seven commits in between are enghitalo/vanilla#208
(the websocket accept key computed on the stack) plus docs and CI. Before
MDA2AV#208, crypto.sha1 leaked ~820 B per upgrade under -gc none, which is what
pushed vanilla-ws to 0.4-1.2 GiB on echo-ws-limited.

vanilla-io_uring keeps main.v identical to vanilla-epoll's, so it gets the
same /ws code. vanilla's io_uring backend cannot take connections over yet:
queue_takeover returns false there and /ws answers 501. vanilla-io_uring
does not subscribe the echo-ws profiles until the library supports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@enghitalo

Copy link
Copy Markdown
Contributor Author

/benchmark-multiple -f vanilla-epoll

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

vanilla-h2c was the same epoll engine with the http2 module behind the
conn-mode seam. vanilla-epoll now does that itself, on two listeners:
- a third listener on :8082 that speaks only h2c. The PRI * preface hands
  the connection to the http2 machine (core.queue_takeover) and appends the
  server SETTINGS. Any HTTP/1.1 request there gets 505, so the h2c profiles
  can't measure h1.
- the :8080 handler, which takes the same preface for unary-grpc. GetSum
  answers with HEADERS, the SumReply as DATA, then grpc-status 0 as
  trailers.

/json over h2c reuses the entry's precomputed prefixes: the body writer is
split out of write_json_into. baseline-h2c, json-h2c and unary-grpc move to
vanilla-epoll's meta.json. The vanilla-h2c directory, its results file, its
container logs and its frameworks.json entry are removed. The http2 code
did not change between the old pin (091c014) and 3dd5a79.

vanilla-io_uring keeps the same code but starts no :8082 listener: its
backend cannot take connections over (enghitalo/vanilla#267), so the
preface on :8080 gets 501.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@enghitalo enghitalo changed the title vanilla-epoll: serve the echo-ws profiles, drop the vanilla-ws entry vanilla-epoll: serve the echo-ws, h2c and unary-grpc profiles, drop vanilla-ws and vanilla-h2c Oct 10, 2026
Touching validate.sh makes CI validate every framework, not just the
changed ones. Keeping a stale vanilla-h2c mention in a comment is cheaper
than that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant