Add blitz: Zig HTTP server with epoll multi-threading#21
Conversation
blitz is a custom HTTP/1.1 server written in Zig, designed for raw throughput benchmarking. Key features: - Pure Zig with no external dependencies (only libc for epoll) - SO_REUSEPORT multi-threading: one epoll loop per CPU core - Edge-triggered epoll with pipeline batching - Zero-copy HTTP request parsing - Pre-computed JSON and static file responses at startup - Minimal heap allocations in the hot path Language: Zig (first Zig entry in HttpArena) Tests: baseline, pipelined, noisy, limited-conn, json, upload
|
Hey @MDA2AV — the first CI run hit a port 8080 conflict (probably from the Kemal validation running at the same time). The second run is waiting on workflow approval since I'm a first-time contributor. Could you approve the workflow run when you get a chance? 🙏 Build works clean locally — Zig compiles in ~16s and the binary is tiny (~3MB). Happy to answer any questions about the implementation! |
The noisy resilience validation test sends bad HTTP methods and expects a 4xx response. Previously blitz would route any method to the handler, returning 200 even for invalid methods like PATCH/DELETE/etc. Now each endpoint validates the HTTP method: - GET+POST: /baseline11, /baseline2, /upload - GET only: /pipeline, /json, /static/* - Returns 405 Method Not Allowed for anything else on known routes
|
Found the validation failure — blitz was returning 200 for invalid HTTP methods (like PATCH/DELETE) on known routes. The noisy resilience test sends a bad method to Fixed by adding method validation to all endpoints:
Zero-cost in the hot path — just a couple of byte comparisons before the actual handler runs. Should be a clean pass now 🤞 |
Benchmark ResultsFramework: Full log |
|
@BennyFranciscus issues with upload |
The upload test sends bodies larger than the 64KB fixed read buffer. When the buffer filled up, parseRequest never found a complete request and connections stalled (0 req/s). Fix: promote to a heap-allocated overflow buffer when: - Fixed buffer is full but no complete request parsed - Already in overflow mode and more data arrives Overflow caps at 4MB (MAX_REQUEST_SIZE). Falls back to fixed buffer when data drains below 64KB. Zero overhead for normal requests that fit in the fixed buffer.
|
Good catch — the upload was hitting 0 req/s because blitz uses a fixed 64KB read buffer, and the upload bodies are way bigger than that. The buffer would fill up, Fixed by adding a dynamic overflow buffer:
Should pass the upload test now. 🤞 |
Benchmark ResultsFramework: Full log |
The read loop was treating EAGAIN/WouldBlock the same as real errors, closing the connection immediately. For large uploads that arrive across multiple epoll events, this killed the connection before the full body arrived (hence 0 req/s on upload benchmark). WouldBlock is the normal exit condition for edge-triggered epoll — it means 'no more data right now, wait for next event'. Now we just break out of the read loop and let the next epoll event deliver more data.
|
Found the upload bug! 🎯 The edge-triggered epoll read loop was treating
Fix: The overflow buffer itself was fine — it just never got a chance to work because the connection was killed on the first partial read. 😅 Should be a clean upload pass now. 🤞 |
The upload test sends 20MB bodies (Content-Length: 20971520) but MAX_REQUEST_SIZE was 4MB. The overflow buffer hit RequestTooLarge and closed connections → 0 req/s with 660K+ read errors. Bumped to 32MB to handle the benchmark's upload payload.
|
Still 0 req/s on upload after the WouldBlock fix — turns out it was a second bug. 🤦 The upload test sends 20MB bodies ( Bumped Should be a clean pass now — upload bodies will accumulate in the overflow buffer up to 32MB, which covers the ~20MB test payload with room to spare. |
Benchmark ResultsFramework: Full log |
|
Upload is working! 🎉 Was a two-part bug:
Both fixed. Memory usage is higher than ideal (4GB at 64c for upload) since each thread buffers the full body, but that's expected for a zero-framework approach without streaming. All profiles passing now ✅ |
Benchmark ResultsFramework: Full log |
blitz ⚡
The first Zig entry in HttpArena!
What is it?
A custom HTTP/1.1 server written in Zig, built from scratch for raw throughput. No framework dependencies — just Zig's standard library and Linux epoll.
Architecture
Test profiles
baseline— GET/POST /baseline11 with query param parsing and body handling (including chunked)pipelined— GET /pipeline returning fixed "ok" responsenoisy— Same as baseline with error resiliencelimited-conn— Same as baselinejson— Pre-computed JSON dataset response (~10 KB)upload— Body ingestion, returns byte countWhy Zig?
Zig provides C-level performance with better safety guarantees, comptime optimizations, and cleaner ergonomics. No hidden allocations, no GC pauses, no runtime overhead. The language is ideal for this kind of systems-level performance work.
Source
Framework repo: https://github.com/BennyFranciscus/blitz
Built with Zig 0.14.0.