Skip to content

v2.4.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 06:12
· 4 commits to main since this release
3dd48a1

A new flag, --disable-keepalive: close the connection after every response
and reconnect for the next request — one TCP connection per request, the
ab/oha model. Opt-in, and the only way to measure what connection setup
costs a server that would rather not let you.

Why the header isn't enough

The obvious way to ask for this is -H 'Connection: close', and it does go out
on the wire. What it does not do is decide anything. zrk learns whether a
socket may be reused from the response: keep_alive starts as
version == HTTP/1.1 — true — and only flips on the server's own Connection
header, or on a body that is close-delimited. Nothing about the request enters
into it.

So the header reconnects only against servers that honour it. Servers that
ignore it keep answering keep-alive, zrk keeps reusing the socket, and they
run at full keep-alive speed while their better-behaved neighbours pay for a
connection per request. That is backwards for the one comparison anybody
reaches for the header to make.

This is not a corner case. In the-benchmarker/web-frameworks, 27 of ~350
frameworks ignore Connection: close — the whole httpbeast-derived Nim family,
the Workerman/Swoole PHP family, agoo-c, may_minihttp, httpz, mist — and they
are among the fastest servers in that field, not the slowest.

What the flag does

Closes from our side after every response, whatever the response said. The
decision is ours because leaving it to the server is exactly the bug above:
every target pays the same price, so the numbers are comparable.

The request advertises Connection: close too, so a compliant server can
release its end with the response instead of parking an idle socket until its
own keep-alive timeout. An explicit -H Connection: ... still wins — the flag
governs our socket, -H governs the bytes, and telling a server keep-alive
while closing anyway stays available as a probe of how it copes.

Against a server that answers Connection: keep-alive and never closes,
-c 8 --closed -d 3s:

throughput
default 59450 req/s
-H 'Connection: close' 59890 req/s
--disable-keepalive 5448 req/s

The middle row is the point: no effect at all, because the server never agreed.

Expect socket errors under this mode on a long run — connection churn exhausts
the client's ephemeral port range, and that ceiling is part of what per-request
connections actually cost. They are reported, not hidden.

Not with --http2

Rejected as a usage error. Connection is a malformed field in HTTP/2 (RFC
9113 §8.2.2, which is why zrk's h2 header block omits it), and one stream per
connection would measure the handshake rather than the protocol.

Also

--format json gains config.disable_keepalive, so a harness collecting both
modes can tell the two apart in the result files rather than tracking it
alongside. Additive: every previously emitted field keeps its name, type and
meaning.

Upgrading

Nothing changes unless you pass --disable-keepalive; keep-alive remains the
default, as it is in HTTP/1.1. zig build test covers 267 tests — 4 new,
exercising the flag end to end: CLI validation and the h2 rejection, the header
it emits and -H taking precedence over it, and a live socket run against a
server that answers keep-alive and never closes, where the connection has to
be retired anyway.