A bounded HTTP/1.1 framework
Fast by design.
Explicit about limits.
The server uses native event-driven networking on Linux, macOS, and Windows. The framework reserves memory and threads before the first request. Small responses share one output buffer.
Native event-driven networking
- Linuxio_uringAsynchronous operations
- macOSkqueueNonblocking sockets
- WindowsIOCPOverlapped operations
EXACT ZIG 0.16.0ASSERTIONS ENABLEDLOOPBACK EXPERIMENT
Default configuration
Default simultaneous limit
Fixed response · Linux
These measurements predate the bounded/http rename. The comparison server is libreactor. Each connection had sixteen outstanding requests. Both servers used three logical processors. The ratio compares medians from three trials per configuration. The measurements describe local experiments, not production capacity.
01 / Design principles
Choose the boundaries.
Then own every transition.
HTTP/1.1 is the request-and-response protocol the server implements. Input/output (I/O) connects application code to operating-system resources.
A socket is an operating-system connection endpoint. A callback is an application function the framework invokes for an event.
An I/O owner is the thread responsible for one server instance’s network state. A slot reserves storage for one connection.
An allocator supplies memory. The framework uses an allocator during startup.
Reserve before serving.
The framework creates buffers, operation records, queues and threads during startup. The demo then seals its framework allocator against further allocations.
Refuse excess admission.
The framework closes excess accepted connections when the admission ceiling is full. The framework creates no additional request slot.
Retain borrowed memory.
A borrow grants temporary access to another component’s storage. The owner retains that storage until every dependent operation has finished.
Assert internal invariants.
An invariant is a condition the implementation must preserve. Assertions check ownership transitions. Malformed HTTP follows ordinary error paths.
The framework bounds its own resources. Application code remains responsible for execution time, shared state and separate allocations.
TigerStyle informs these choices. The implementation retains explicit exceptions, including small payload copies and resources outside its allocator.
02 / Inside main.zig
The entry point establishes policy.
src/main.zig connects the demo application to the framework. The entry point parses arguments, loads HTML and establishes resource limits.
std.process.Init supplies process capabilities. The demo uses init.io for startup file access. Custom socket backends handle request I/O.
Budget tracks requested framework bytes and refuses growth beyond its configured ceiling.
A cluster is a group of server instances with shared admission and startup coordination. Cluster implements that group.
Cluster.start() prepares threads. Cluster.run() opens the startup gate after the demo seals Budget.The demo installs signal handlers before preparing threads. Those handlers set atomic stop flags without allocating or invoking callbacks.
The demo collects statistics after the owners stop. An uncertain failure requires process termination instead of freeing borrowed storage.
The architecture guide maps each startup step to the maintained source.
03 / Cluster and ownership
One owner per shard.
One ceiling across the cluster.
- Shard
- A shard is one
Serverinstance with its own backend and connection storage. - Owner
- An owner is the thread responsible for a shard’s I/O state. Shard zero uses the caller’s thread.
- Listener
- A listener is a socket that accepts connections. Linux shards use separate listeners on the same port.
- Backend
- A backend adapts operating-system I/O to the server’s operation interface.
- Slot
- A slot contains one admitted connection’s input, output, parser state and operation identities.
- Arena
- An arena is a contiguous output buffer. Several responses can occupy different ranges within that buffer.
Inline execution runs application callbacks on the I/O owner. Each CPU denotes a logical processor.
Linux selects one inline shard per allowed CPU, with an automatic maximum of sixteen.
Explicit Linux and Windows configurations support up to sixty-four shards within the resource budget. macOS supports one shard.
The macOS decision follows the recorded listener-distribution experiment. That observation does not establish universal behavior across macOS releases.
Native event-driven networking.
An event-driven backend advances connections when the operating system reports readiness or completion.
While one connection waits for network progress, its owner can service other ready connections.
The build target selects the native backend automatically.
Linux uses a custom io_uring backend. The Linux interface provides submission and completion queues for asynchronous operations.
macOS uses nonblocking sockets and kqueue. The macOS interface reports readiness. The adapter translates readiness into operation results.
Windows defaults to one shard. Select additional inline owners with --shards N.
Windows uses overlapped sockets and one I/O completion port (IOCP) per shard. Each port delivers operation results to its owner.
Fixed operation records retain buffers through completion and cancellation. Windows uses the same callback, writer, batching, and admission contracts.
Windows: one listener, independent owners.
Shard zero accepts connections and handles its own round-robin share. Fixed queues transfer other sockets to their destination owners.
Each transfer carries a socket handle and acceptance time. Request bytes enter the destination’s own buffer after the transfer.
The destination associates the socket with its IOCP before receiving bytes. Established connections remain with that owner.
The shared connection ceiling includes queue residence and adoption. Each queue has startup-fixed capacity.
Shutdown stops publication, drains queued sockets and operations, then joins owners. The cluster frees storage only after ownership ends.
04 / Request and response
The callback returns an action.
The owner provides progress.
The parser validates syntax and message framing before the first callback. The server receives the complete request within configured bounds.
Header fields unrelated to framing or connection control remain uninterpreted until application lookup. Lazy interpretation does not bypass header syntax validation.
api.Context provides the request, writer, event, application pointer, cancellation flag and eight state words.
The framework zeros those words before each request. The words retain application progress across that request’s flush continuations.
.request identifies the initial callback. .flushed identifies a callback after the previous flush completes.
A response snapshot contains committed output bytes. A batch groups response snapshots from one connection.
A completion reports an operation’s result. A terminal completion ends the operation’s access to borrowed memory.
| Writer operation | Application responsibility |
|---|---|
begin(status, type, length) | Call once per response. A null length selects chunked framing for responses with bodies. |
reserve(n) / commit(n) | Generate bytes directly into output storage. Commit only initialized bytes. |
write(bytes) | Copy bytes into output storage. Respect remaining capacity. |
borrow(bytes) | Use request-owned bytes or immutable storage with server lifetime. |
return writer.flush() | Yield immediately. The owner sends every committed byte before the successful resumed callback. |
return writer.finish() | End the response. Do not retain request or writer pointers. |
flush() creates an action. The function does not wait for the network. The callback must return the action immediately.
The owner can use several sends to exhaust committed output. Local acceptance does not prove peer receipt.
WouldBlock means the current output storage lacks capacity. Flush committed bytes and continue after resume.
Split generated output into pieces within Writer.capacity(). Repeated flushes cannot satisfy a reservation larger than the arena.
context.state preserves continuation state across callbacks. A continuation records where application work resumes.
The owner suppresses response body bytes for HEAD requests. The application still accounts for the declared logical body through writer operations.
Inline callbacks run on the owner thread. Blocking work delays that shard. The framework cannot preempt the callback.
Explicit worker mode assigns callbacks to fixed startup threads. Worker mode requires one shard and provides no arbitrary application isolation.
Callbacks on different inline shards can execute concurrently. The application must synchronize shared mutable state.
05 / Less work per response
Remove repeated work.
Preserve ownership.
The implementation changes data layout, dispatch and transport bookkeeping. The same callback and writer contract remains visible to applications.
Run bounded callbacks inline.
The default removes application-worker handoffs. The owner invokes the callback directly. Applications select workers when callbacks require finite blocking work.
Generate the head once.
begin() writes the response head into the arena. A cached Date/status prefix avoids repeated general formatting.
Worker dispatch copies the cache into exclusive slot storage. Inline callbacks read their owner’s cache.
Combine small responses.
Generated bodies follow their heads. Borrowed spans up to 256 bytes can be copied into the arena when space permits.
The transport uses SEND for one selected span. The transport uses SENDMSG for several selected vectors.
Bound the batch.
The default permits 128 response snapshots per connection. The owner submits available output without waiting for a full batch.
Input compaction occurs only after relevant borrows end. The counter pipeline_copy_bytes records those copies.
Visit ready work.
A ready ring stores slots awaiting service in first-in, first-out order. A per-shard callback budget bounds each iteration’s dispatch count.
The owner samples time in groups of sixteen callbacks. A periodic sweep checks idle slots for expired deadlines.
Reduce parser overhead.
The parser scans lines in sixteen-byte groups. Classification tables and fixed-name comparisons reduce repeated checks while preserving validation.
The parser writes the request view in place. Applications inspect other header values only when needed.
Address operation records directly.
An operation cell is reserved storage for one transport operation. Fixed cell addresses replace transport lookup scans.
The transport references vectors stored by the caller. The caller retains vector metadata until target completion.
Add independent I/O owners.
Linux shards distribute connection handling across allowed CPUs. Each owner retains its own transport and connection state.
The shared admission counter preserves one connection ceiling. Extra shards also increase reserved memory.
Borrowed parsing avoids a second request payload buffer. Small response copies are deliberate and counted. Ordinary kernel socket copies remain.
The server does not implement SEND_ZC, zero-copy receive or sendfile. Early receive submission and eager submission remain disabled by default.
The combined comparison measures the resulting implementation. The comparison does not assign a separate speedup to each mechanism.
06 / Measured results
A smaller gap.
A visible remaining limit.
A pipeline contains requests sent before the client receives earlier responses. Pipeline depth counts those outstanding requests per connection.
On narrow screens, scroll graphics horizontally to inspect every label.
The harness used 128 connections and depths 1, 16 and 128. Each configuration had three shuffled repetitions.
Each trial included a one-second warmup and five timed seconds. Four wrk client threads used CPUs 3–7.
Server configurations used CPU 0 or CPUs 0–2. Zig selected one or three shards within those masks.
Libreactor used one or three serving processes plus an idle parent. Both contenders returned the thirteen-byte body Hello, World!.
| Server CPUs | Pipeline depth | Zig | libreactor | Zig / libreactor |
|---|---|---|---|---|
| 1 | 1 | 364,447 | 329,510 | 1.106 |
| 1 | 16 | 3,404,295 | 4,374,097 | 0.778 |
| 1 | 128 | 8,653,315 | 14,517,670 | 0.596 |
| 3 | 1 | 558,216 | 583,309 | 0.957 |
| 3 | 16 | 5,630,758 | 6,195,784 | 0.909 |
| 3 | 128 | 11,937,031 | 13,875,065 | 0.860 |
All thirty-six trials and warmups passed. The harness counted 1,053,649,993 timed responses and validated 3,840 exact preflight responses.
The preflight checks validate complete bodies and required headers. Timed wrk checks validate framing, status and errors.
Final ownership counters and late framework allocation counters were zero. Rejection and timeout counters were also zero.
The one-core, depth-128 Zig samples ranged from 6.89 to 8.71 million responses per second. That variation remains part of the evidence.
Environment: omarx1, Intel Core Ultra 7 258V, x86_64 Omarchy 4.0.2, kernel 7.1.9-arch1-2, glibc 2.44. Zig used 0.16.0 ReleaseSafe with assertions.
Recorded power-profile and energy-preference endpoints were performance. Those observations do not establish host isolation or average CPU frequency.
Low aggregate CPU use does not identify the bottleneck. The remaining gap requires profiling or controlled changes. Corrected wrk percentiles cannot establish request-tail guarantees.
Source: the qualified adoption report. These results are not a TechEmpower ranking.
07 / Use the framework
Keep application policy
at the entry point.
The embedding example is a separate consuming project. Its build imports the package module named bounded_http.
- Select the exact compiler.
Use Zig 0.16.0. Use ReleaseSafe for measurements. The project rejects modes that disable its required assertions.
- Declare the dependency.
The example uses a local package path. Change that path when moving the example into another project.
- Establish limits and application state.
Select connection, input, output, time and memory ceilings. Keep shared state alive until the cluster has stopped.
- Provide the callback.
Use
api.Handler. A typed helper can return errors. The callback bridge converts unrecoverable handler errors into.close. - Prepare, seal, then run.
Initialize
BudgetandCluster. Prepare threads, seal the allocator and enterCluster.run(). - Preserve failure ownership.
Collect statistics after a successful run. Do not unwind borrowed storage after an uncertain failure.
Reserve output before application side effects.
A response adapter translates higher-level application output into writer operations. An adapter can configure a complete draft reservation before startup.
The owner drains earlier batched responses before initial dispatch when that reservation cannot fit. The framework does not replay application side effects.
Checked draft operations support additional headers and generated bodies. Existing streaming and borrowed-output operations remain available.
cd examples/embedding
zig build run -Doptimize=ReleaseSafe -- --port 8081 --duration-ms 30000
From another terminal:
curl --http1.1 http://127.0.0.1:8081/
The example stops after its configured duration. The full demo separately shows signal handling and several response routes.
Read the integration guide for writer lifetimes, output capacity, HEAD behavior and shutdown requirements.
08 / Failure and scope
Reliability needs
honest boundaries.
| Condition | Current behavior |
|---|---|
| Admission ceiling reached | The owner closes the excess accepted socket. The framework does not promise an HTTP 503 response. |
| Request exceeds a configured bound | The parser rejects the request. Body, wire, header and header-count bounds remain separate. |
| Peer closes its sending direction | Complete buffered requests and output can still drain. An incomplete suffix receives an ordered 400 response. |
| Cancellation requested | The owner retains memory through target completion and cancellation acknowledgement. |
| Startup partly fails | The cluster aborts prepared owners before request I/O begins. |
| A secondary shard fails | The cluster asks all shards to stop. A finite watchdog bounds exit acknowledgement. |
| Ownership remains uncertain | The process terminates instead of freeing storage with outstanding borrows. |
| An inline callback never returns | The framework cannot preempt that callback. An external watchdog must supervise a violating application. |
The current server binds IPv4 loopback and serves plain HTTP. Transport Layer Security (TLS) and production deployment qualification remain pending.
The Windows shard receipt records native x64 distribution, admission, and shutdown tests. Windows performance remains unmeasured.
Winsock creates socket resources during admission. Fixed framework storage does not bound provider allocations or delayed kernel cleanup.
Dynamic borrow-release notifications and per-request worker offload remain pending. Applications cannot safely invent those ownership mechanisms around the current API.
Future measurements include HTML workloads, macOS contenders, external network traffic and trustworthy response-tail statistics.
Runtime evidence applies to the exact recorded hosts and revisions. A cross-compiled binary does not establish runtime behavior on another platform.
09 / Sources and further reading
Read the implementation.
Reproduce the evidence.
The core architecture and recorded runtime evidence use source at 4b3cd5551d80b422ec6ef763627d019e6f1dfb83. The measured candidate was bbcec8aa9516efc470238b9edeea4f01b1f4a6d7.
The measurements retain the earlier zig-http name and original binaries. The rename changes product identification, not the measured evidence.
The response-adapter addition is pinned at e52f09f723388685263d14a9bfa265f2456a3744. Its native correctness checks add no performance measurement.
- Architecture guide · concepts, startup order, scheduling, resources and shutdown.
- Integration guide · dependency setup, callbacks, response writing and operational checks.
- Demo entry point, cluster and server, application API, ownership contract.
- Immutable runtime publication · Zig 0.16.0, custom io_uring and kqueue backends.
- Windows IOCP shard implementation · Native x64 correctness verified; results and exclusions.
- Qualified comparison and evidence manifest · exact versions, binary hashes, sample ranges and host conditions.
- Read-only evidence verifier · checks raw results without starting a server.
- TigerStyle synthesis · source-driven guidance for resource bounds, assertions and ownership.
- Repository writing policy · the user’s Simplified Technical English and Zinsser guidance.