Technology Lab · Systems engineering06 September 2026

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

EXACT ZIG 0.16.0ASSERTIONS ENABLEDLOOPBACK EXPERIMENT

0Extra application threads
Default configuration
128Connections served
Default simultaneous limit
90.9%Of comparison throughput
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.

PRINCIPLE 01

Reserve before serving.

The framework creates buffers, operation records, queues and threads during startup. The demo then seals its framework allocator against further allocations.

PRINCIPLE 02

Refuse excess admission.

The framework closes excess accepted connections when the admission ceiling is full. The framework creates no additional request slot.

PRINCIPLE 03

Retain borrowed memory.

A borrow grants temporary access to another component’s storage. The owner retains that storage until every dependent operation has finished.

PRINCIPLE 04

Assert internal invariants.

An invariant is a condition the implementation must preserve. Assertions check ownership transitions. Malformed HTTP follows ordinary error paths.

The boundary is a resource contract.

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.

Startup separates resource creation from request I/Omain validates configuration, loads assets and initializes the cluster. start prepares threads. main seals the budget before run releases the startup gate. 01PREPARERead settingsmain parses arguments andvalidates Config.Load assetsmain reads HTML before requestI/O.Reserve resourcesBudget and Cluster allocatestartup storage.02PREPARE THREADSInstall stop handlingThe demo installs signal orconsole handlers.Call Cluster.start()Secondary owners wait behind astartup gate.Seal Budgetmain refuses later frameworkallocations.03SERVE AND CLOSEPrint READYStartup has completed.Call Cluster.run()The gate opens. Owners processrequests.Finish shutdownOwners drain. main reports statsand deinitializes.No request I/O crosses the gate before the allocator is sealed.
FIGURE 01 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 Server instance 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.
One cluster, several I/O ownersThree Linux shards have separate listeners, backends and slot storage. One shared counter bounds admitted connections. Shard zero uses the caller thread. HTTP clientsLinux distributes connections across listenersCLUSTERExample: three Linux shardsShard 0Caller threadListener + I/O backend128 reserved slotsInput + output + operation stateShard 1Startup threadListener + I/O backend128 reserved slotsInput + output + operation stateShard 2Startup threadListener + I/O backend128 reserved slotsInput + output + operation stateShared admission: 128 total · Shared application state: synchronize mutable data
FIGURE 02 Each shard reserves the full slot capacity. The shared admission counter enforces the total connection ceiling.

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.

Windows: one listener, independent IOCP owners Shard zero accepts without reading request bytes. A shared admission charge follows each socket through a fixed queue into its destination IOCP. Shard zero also handles connections. Every owner keeps private buffers. Receivers drain queues after acquiring the producer completion flag. HTTP clients One exclusive listener · owned by shard 0 AcceptEx completes without receiving request bytes Reserve admission · choose the next destination The socket has no IOCP association and no borrowed request buffer Local share No queue or extra thread Fixed queue · capacity C Socket handle + acceptance time Fixed queue · capacity C Socket handle + acceptance time Shard 0 · caller thread Own IOCP + C private slots Associate socket, then receive Input + arena + operation records Shard 1 · startup thread Own IOCP + C private slots Associate socket, then receive Input + arena + operation records Shard 2 · startup thread Own IOCP + C private slots Associate socket, then receive Input + arena + operation records One admission ceiling: queued + transferring + adopted sockets ≤ C Shutdown: stop publication → drain queues and operations → join owners → release storage
WINDOWS HANDOFF Queued sockets retain their admission charge and original deadline. No extra acceptor thread is created.

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.

Memory ownership across all platforms.

Every backend reserves connection storage per shard. The table below records a Linux configuration.

More shards require more reserved memory.

Three shards reserve three sets of connection storage. The shared admission ceiling does not divide the reserved slot count between shards.

Recorded Linux example · 128 total connections
ResourceOne shardThree shards
Requested framework heap29,635,986 bytes88,907,590 bytes
Requested secondary-owner stacksNoneTwo × 1 MiB
Admitted connections128 total128 total

The figures exclude kernel queues, allocator overhead, application allocations and actual stack mappings. The heap metric is not process memory usage.

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.

Request handling returns actions to the I/O ownerThe owner receives and parses a complete bounded request. A callback returns flush, finish or close. Finished responses can accumulate in a bounded batch before submission. Flush resumes after local transport acceptance. Finish has no flushed event. Closing retains storage until every owner returns. ONE CONNECTION · DRAIN PATHReceiveReserved input storageParseFraming and syntax validationHandleExclusive request/writer accessflush or batch drainfinish + capacity → next buffered requestFreeze and submitKeep response bytes unchanged.Terminal send completionRecord accepted bytes.All snapshot bytes accepted?Only now advance the application.Partial sendSubmit remaining bytes.flush → .flushedResume the same request.Batch drainedContinue with the next input.Close or failureCancel outstanding work. Reuse the slot only after application, target and cancel owners return.
FIGURE 03 Flush resumes the same request after local transport acceptance. Finish produces no further callback for that response.
Writer operationApplication 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.

A bounded arena reduces small-response send operationsHeaders, generated bodies and small copied borrows occupy adjacent arena ranges. Larger borrowed bodies remain separate vectors. SEND handles one selected span; SENDMSG handles several. Partial progress can require more operations. ONE CONNECTION · OUTPUT ARENASmall responses share one contiguous span.Head ABody AHead BBody BHead CBody CFreeOne selected span → SENDLarge borrowed bodies remain separate.Arena prefixBorrowed bodyRequest or immutable assetArena suffixSeveral selected spans → SENDMSGEvery referenced byte remains valid until the target send completes.Small borrowed spans may be copied. Kernel socket copies remain.
FIGURE 04 A vector describes a byte span. The transport retains vectors and referenced bytes until completion. Partial progress can require additional sends.
01

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.

02

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.

03

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.

04

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.

05

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.

06

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.

07

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.

08

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.

Copy avoidance has an explicit boundary.

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!.

image/svg+xml tools/render_performance_figures.py 0 2 4 6 8 10 12 14 16 Completed responses per second (millions) Depth 1 Depth 16 Depth 128 0.558 M/s 5.631 M/s 11.937 M/s 0.583 M/s 6.196 M/s 13.875 M/s bounded/http libreactor Three allowed server CPUs 128 connections · 3 trials · 5 seconds each Bars: medians · Whiskers: observed min–max · Closed-loop HTTP/1.1 Three allowed server CPUsDepth 1: bounded/http (recorded as zig-http) median 0.558216 million responses/s; range 0.553249 to 0.568547. Depth 16: bounded/http (recorded as zig-http) median 5.630758 million responses/s; range 5.615917 to 5.646356. Depth 128: bounded/http (recorded as zig-http) median 11.937031 million responses/s; range 11.897279 to 11.988298. Depth 1: libreactor median 0.583309 million responses/s; range 0.576265 to 0.588287. Depth 16: libreactor median 6.195784 million responses/s; range 6.190575 to 6.198996. Depth 128: libreactor median 13.875065 million responses/s; range 13.767128 to 14.025376.
FIGURE 05A Three server CPUs. Bars show medians; whiskers show the minimum and maximum across three trials.
image/svg+xml tools/render_performance_figures.py 0 2 4 6 8 10 12 14 16 Completed responses per second (millions) Depth 1 Depth 16 Depth 128 0.364 M/s 3.404 M/s 8.653 M/s 0.330 M/s 4.374 M/s 14.518 M/s bounded/http libreactor One allowed server CPU 128 connections · 3 trials · 5 seconds each Bars: medians · Whiskers: observed min–max · Closed-loop HTTP/1.1 One allowed server CPUDepth 1: bounded/http (recorded as zig-http) median 0.364447 million responses/s; range 0.362423 to 0.366211. Depth 16: bounded/http (recorded as zig-http) median 3.404295 million responses/s; range 3.360874 to 3.412642. Depth 128: bounded/http (recorded as zig-http) median 8.653315 million responses/s; range 6.887168 to 8.707278. Depth 1: libreactor median 0.329510 million responses/s; range 0.329350 to 0.330812. Depth 16: libreactor median 4.374097 million responses/s; range 4.280703 to 4.452351. Depth 128: libreactor median 14.517670 million responses/s; range 14.417633 to 14.602107.
FIGURE 05B One server CPU. The deepest pipeline retains a substantial efficiency gap.
Qualified Linux comparison · median responses per second
Server CPUsPipeline depthZiglibreactorZig / libreactor
11364,447329,5101.106
1163,404,2954,374,0970.778
11288,653,31514,517,6700.596
31558,216583,3090.957
3165,630,7586,195,7840.909
312811,937,03113,875,0650.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.

  1. Select the exact compiler.

    Use Zig 0.16.0. Use ReleaseSafe for measurements. The project rejects modes that disable its required assertions.

  2. Declare the dependency.

    The example uses a local package path. Change that path when moving the example into another project.

  3. Establish limits and application state.

    Select connection, input, output, time and memory ceilings. Keep shared state alive until the cluster has stopped.

  4. Provide the callback.

    Use api.Handler. A typed helper can return errors. The callback bridge converts unrecoverable handler errors into .close.

  5. Prepare, seal, then run.

    Initialize Budget and Cluster. Prepare threads, seal the allocator and enter Cluster.run().

  6. 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.

ConditionCurrent behavior
Admission ceiling reachedThe owner closes the excess accepted socket. The framework does not promise an HTTP 503 response.
Request exceeds a configured boundThe parser rejects the request. Body, wire, header and header-count bounds remain separate.
Peer closes its sending directionComplete buffered requests and output can still drain. An incomplete suffix receives an ordered 400 response.
Cancellation requestedThe owner retains memory through target completion and cancellation acknowledgement.
Startup partly failsThe cluster aborts prepared owners before request I/O begins.
A secondary shard failsThe cluster asks all shards to stop. A finite watchdog bounds exit acknowledgement.
Ownership remains uncertainThe process terminates instead of freeing storage with outstanding borrows.
An inline callback never returnsThe 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.

  1. Architecture guide · concepts, startup order, scheduling, resources and shutdown.
  2. Integration guide · dependency setup, callbacks, response writing and operational checks.
  3. Demo entry point, cluster and server, application API, ownership contract.
  4. Immutable runtime publication · Zig 0.16.0, custom io_uring and kqueue backends.
  5. Windows IOCP shard implementation · Native x64 correctness verified; results and exclusions.
  6. Qualified comparison and evidence manifest · exact versions, binary hashes, sample ranges and host conditions.
  7. Read-only evidence verifier · checks raw results without starting a server.
  8. TigerStyle synthesis · source-driven guidance for resource bounds, assertions and ownership.
  9. Repository writing policy · the user’s Simplified Technical English and Zinsser guidance.