Releases: prothegee/zix
Releases · prothegee/zix
0.3.0
0.3.0 (2026-06-10)
Update:
- Http1 router prefix param:
zix.Http1.Routergains.PREFIXand.PARAMroute kinds (addedRouteKindand akindfield onzix.Http1.Route, default.EXACT), reaching parity with thezix.Httprouter and itsexact > param > prefixpriority (ADR-004). Captured path params are read with the new free functionzix.Http1.pathParam(name)(a per-handler thread-local, since the Http1 handler has noRequest, see ADR-029), capped at 8 params per match.- The prefix pass now guards the boundary byte behind
startsWith. The same fix was applied to thezix.Httprouter, which read one byte past a request path shorter than a registered prefix (a panic in Debug/ReleaseSafe, a masked out-of-bounds read in ReleaseFast). - Backward compatible:
.kinddefaults to.EXACT, so existing exact-only Http1 route tables are unchanged.examples/http1_static.zignow routes/secretvia a.PREFIXroute. See ADR-033.
- Epoll max events 512:
- The epoll batch (max events drained per
epoll_wait) is raised from 256 to 512 across all native epoll servers (zix.Tcp,zix.Http,zix.Fix,zix.Grpc,zix.Http1) and unified into one named, documented file-level constantEPOLL_MAX_EVENTS: usize = 512per server. The previous mix of a lowercaseepoll_max_eventsconst and inline256literals is removed. - 512 lets a worker clear its ready-fd set in a single syscall at high connection counts: a worker holding more than 256 readable fds no longer needs a second
epoll_wait. No public API change, the constant is an internal tuned default. See ADR-032.
- The epoll batch (max events drained per
- Httpconfig naming consistency:
HttpServerConfigfield renames for API-wide consistency (defaults unchanged):max_kernel_backlogbecomeskernel_backlog(now matchingTcp,Fix,Http1,http2, andGrpc, which already used the bare name), andmax_client_requestbecomesmax_recv_buf(matchingzix.Http1).- Migration: rename the fields at the call site.
.max_kernel_backlog = Nbecomes.kernel_backlog = N, and.max_client_request = Nbecomes.max_recv_buf = N.max_allocator_sizeandmax_client_responseare unchanged (no equivalent exists outsidezix.Http).
- Http1 handler at init:
zix.Http1.Server.initnow takes the comptime handler as its first argument and bakes it into the server type, sorun()takes no argument. This matcheszix.Httpandzix.Grpc, which register routes at init. The server core stays routing-agnostic: the handler may be aRouter(routes).dispatch, a bareHandlerFn, or a middleware chain.- Migration:
Server.init(.{ ... })thenserver.run(Routes.dispatch)becomesServer.init(Routes.dispatch, .{ ... })thenserver.run().
- Grpc epoll multiplexed:
zix.Grpc.EPOLLwas rewritten from a blocking thread-per-connection pool into a shared-nothing multiplexed event loop. Each worker owns a privateSO_REUSEPORTlistener, its own epoll instance, and a private fd-indexed connection table, the kernel balances connections across workers. One worker drives many non-blocking connections through a resumable HTTP/2 state machine (GrpcMuxConn/grpcMuxOnReadable), so concurrency is bounded by connection count, not thread count.- Every route, including server-streaming, is dispatched inline on the worker under
.EPOLL(no per-stream thread, no connection write mutex). A streaming handler runs on the event loop and must stay bounded, use.ASYNCfor unbounded streams. The blockingserveGrpcConnpath is unchanged for.ASYNC/.POOL/.MIXED. pool_sizeis now the multiplexing worker count for.EPOLL(0 = cpu count), not a blocking pool size. See ADR-031.
- Grpc unary hotpath:
- Unary and streaming replies (initial HEADERS, every DATA, the trailer, and control frames) are coalesced into one
write()per readable event via a per-connectionReplyStagecork. SETTINGS_INITIAL_WINDOW_SIZEraised to 16 MB with a one-time connection-window bump, so small request bodies no longer trigger a per-DATAWINDOW_UPDATE, the connection window is replenished in bulk only past a threshold.- Buffered frame reads (a HEADERS plus DATA pair costs one
read()), and per-streambody/header_scratchmoved to per-connection backing slices sized tomax_body/max_header_scratchinstead of fixed inline arrays. - The constant reply header blocks (
:status 200+content-type: application/grpc+proto, and thegrpc-status: 0trailer) are HPACK-encoded once at comptime and memcpy'd on the hot path.HpackEncoder.writeStringnow types the Huffman result as?usizeso the encoder runs at comptime. Other content-types / statuses use the dynamic encoder. - Combined effect: unary ~110k to ~420k req/s (exceeds Kestrel at 256 connections), streaming ~2.6k to ~28k calls/s. See ADR-031.
- Unary and streaming replies (initial HEADERS, every DATA, the trailer, and control frames) are coalesced into one
- Gttp1 logger field:
Http1ServerConfig.logger: ?*Loggeradded. The server routes lifecycle lines (listening, EPOLL fallback) through it.- Per-request access logging is handler-side: the Http1 handler writes to the fd and returns void, so the server cannot observe response status or bytes. Handlers call
logger.access()themselves (examples use a module global).
- Gttp1 examples parity and completion:
- The 9 existing
http1_*examples were brought tohttp_*presentation parity (full tunable constant block, commented logger scaffolding in the basic family). - 6 new examples complete the set (15 total):
http1_manual_concurrent,http1_sse,http1_xtra_headers,http1_client,http1_timeout_resp,http1_websocket.
- The 9 existing
- Gttp1 handler timeout:
Http1ServerConfig.handler_timeout_mspluszix.Http1.setTimeout()andzix.Http1.isExpired(). The server arms a thread-local deadline before each dispatch across all four models.statusPhrasegained408 Request Timeout. See ADR-029.
- Http1 websocket:
- New
zix.Http1.WebSocketmodule: RFC 6455 frame codec (parseFrame/buildFrame/buildHeader/acceptKey) andupgrade()over raw fd I/O. - Engine-owned frame loop under
.EPOLL: a handler callsWebSocket.serve(fd, key, on_frame)to hand the connection to the epoll loop. The engine echoes viaon_frameper readable event (fn(fd, opcode, payload) void), auto-ponging ping and auto-echoing close. No worker is parked per connection. WebSocket.sendcoalesces every frame produced during one readable event into a singlewrite(), so a pipelined burst costs one syscall instead of one per frame.zix.Http1.WsFrameFnexported. Engine-owned WebSocket is.EPOLLonly: under.ASYNC/.POOLthe handoff is cleared and the connection ends. See ADR-030.
- New
- Http1 large body drain:
- Under
.EPOLL, a request body larger thanmax_recv_bufno longer returns431. The engine dispatches the handler with an empty body (large-body endpoints use the Content-Length value), then reads and discards the remaining body bytes across events so the connection stays usable for keep-alive. Bodies that fit the buffer are unchanged.
- Under
- Http client version selector:
zix.Http.Clientgained aversionconfig field (zix.Http.ClientVersion:HTTP_1,HTTP_2,HTTP_3, defaultHTTP_1).HTTP_2andHTTP_3returnerror.UnsupportedVersionuntil backends are wired. See ADR-028.
- Http1 writesimple hotpath:
zix.Http1.writeSimplenow builds the response header with a direct byte encoder (buildSimpleHeaderviaappendStatusCode/appendDec/appendBytes), replacingstd.fmt.bufPrint.- Small bodies (up to 3840 bytes) are copied with the header into one contiguous stack buffer and sent with a single
write(). Bodies above 3840 bytes fall back to inlinewritevto avoid copying a large payload. cachedDate()callsclock_gettimeonly every 256 requests via a thread-local tick counter, not per-request.- Measured ~450k to ~612k req/s at c128 vs the prior
writev-only path. See ADR-026.
- Response header default minimal:
HttpServerConfig.max_response_headersdefault lowered from.COMMON(32) to.MINIMAL(16).zix.Http1:MAX_HEADERScap 32 to 16, newHttp1ServerConfig.max_headers: u8 = 16.- Behavioral change: handlers adding 17 to 32 custom headers now hit
error.TooManyHeadersuntil the tier is raised. See ADR-027.
Fix:
- Http1 websocket epoll echo:
zix.Http1WebSocket echo did not work under.EPOLL: the handshake succeeded but no frame was ever echoed. The handler's blockingread()loop returnedEAGAINat once on the engine's non-blocking sockets. The engine-owned frame loop (WebSocket.serve, see ADR-030) replaces that pattern. Thehttp1_websocketexample now uses.EPOLL.
0.2.2
0.2.2 (2026-06-06)
Update:
- Grpc unary inline dispatch:
- Unary routes (
Route.is_server_streaming = false, the default) now dispatch synchronously on the connection thread. No per-call Task alloc, no 4 KBheader_scratchcopy, noio.asyncenqueue, no ConnMutex acquire/release. - Server-streaming routes require
is_server_streaming = trueon theRouteentry to use thread-per-stream dispatch. - New field on
zix.Grpc.Route:is_server_streaming: bool = false.
- Unary routes (
- Grpc bench fixtures:
- Added
examples/grpc_hello_req.binandexamples/grpc_location_req.bin: properly gRPC-framed binary fixtures for h2load and ghz benchmarking. - h2load and ghz benchmark commands added to all 8 gRPC server examples.
- Added
Fix:
- n/a
end of ## 0.2.2 (2026-06-06)
0.2.1
0.2.1 (2026-06-05)
Update:
- n/a
Fix:
- Grpc content type:
- https://codeberg.org/prothegee/zix/issues/67
sendGrpcErroromittedcontent-typein the trailers-only HEADERS frame. gRPC clients rejected the response with a content-type error. All HEADERS frames sent by the server now includecontent-type: application/grpc+protoper the gRPC spec.
- Grpc concurrent stream:
- https://codeberg.org/prothegee/zix/issues/68
- Concurrent server-streaming RPCs on the same h2 connection could deadlock when the TCP send buffer filled under backpressure. Each stream is now dispatched on a dedicated thread sharing a connection-level write mutex, preventing frame interleaving.
end of 0.2.1 (2026-06-05)
0.2.0
0.2.0 (2026-06-02)
Update:
- Adding TCP raw
- Adding gRPC h2c
- Adding FIX (over TCP)
- Adding EPOLL dispatch model
- ASYNC is default dispatch model
- Handler/router (Http & gRPC) now use comptime
- Documentation split into English (en) and Bahasa (id)
Fix:
- n/a
end of 0.2.0 (2026-06-02)
0.1.0
0.1.0 (2026-05-16)
Update:
- Initial release, Zig 0.16.x network library (minimum_zig_version: 0.16.0-dev.2974+83c7aba12):
- HTTP:
- Server with three dispatch models: POOL, ASYNC, MIXED
- Router with exact, param, and prefix matching
- Middleware (comptime, zero-allocation)
- WebSocket upgrade
- Server-Sent Events (SSE)
- Multipart upload
- Static file serving
- HTTP client
- UDP:
- Generic server and client over user-defined packet type
- Broadcast peer snapshot per packet
- Unix Domain Sockets (UDS):
- Framed server and client
- Channel:
- In-process ring-buffer message passing, generic over element type
- Utils:
- File save helper, MIME type resolution
- HTTP:
*Fix:
- n/a