From 6a59ca608ed21418c90cedf992401f85dc12c99d Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 13 Jul 2026 03:02:54 +0700 Subject: [PATCH 01/13] adding ignore --- frameworks/zix-ws/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 frameworks/zix-ws/.gitignore diff --git a/frameworks/zix-ws/.gitignore b/frameworks/zix-ws/.gitignore new file mode 100644 index 000000000..595d20d1a --- /dev/null +++ b/frameworks/zix-ws/.gitignore @@ -0,0 +1,4 @@ +.zig-cache +zig-out +zig-package +vendor From ebb67ba71b4c894eef3ade3fef7e2ea797cce460 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 13 Jul 2026 03:03:35 +0700 Subject: [PATCH 02/13] re-define display_name, engine, and description --- frameworks/zix-ws/meta.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frameworks/zix-ws/meta.json b/frameworks/zix-ws/meta.json index aa33e4c45..c091b5486 100644 --- a/frameworks/zix-ws/meta.json +++ b/frameworks/zix-ws/meta.json @@ -1,9 +1,9 @@ { - "display_name": "zix-ws", + "display_name": "zix", "language": "Zig", "type": "engine", - "engine": "zix", - "description": "Zig WebSocket echo server on the zix.Http1 raw engine (no std.http). Shared-nothing by design: each worker owns its own SO_REUSEPORT multishot accept, io_uring completion ring, and connections, with no shared state or locking across cores. The upgrade and echo are engine-owned: frames are read from a shared per-worker provided-buffer ring (an idle connection holds no buffer), and a pipelined burst is coalesced into one write.", + "engine": "zix.Http1 WebSocket URING Dispatch Model", + "description": "Zig WebSocket echo on the zix.Http1 engine-owned WebSocket path, .URING dispatch: GET /ws upgrades, WebSocket.serve echoes inside the engine, a pipelined burst coalesced into one write.", "repo": "https://codeberg.org/prothegee/zix", "enabled": true, "tests": [ From 0590a5fe48772ccd9b32b9371c80acc1650a8c14 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 13 Jul 2026 17:31:53 +0700 Subject: [PATCH 03/13] updating dockerfile for zix-ws entry --- frameworks/zix-ws/Dockerfile | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/frameworks/zix-ws/Dockerfile b/frameworks/zix-ws/Dockerfile index 13da3e499..ee2bfc7ca 100644 --- a/frameworks/zix-ws/Dockerfile +++ b/frameworks/zix-ws/Dockerfile @@ -4,21 +4,26 @@ FROM alpine:3.20 AS build ARG RETRY=6 ARG TARGETARCH ARG RETRY_DELAY=3 +ARG TIMEOUT_SEC=180 ARG ZIG_VERSION=0.16.0 ARG ZIX_VERSION=0.4.x-rc3 -RUN apk add --no-cache ca-certificates curl git tar xz +RUN apk add --no-cache ca-certificates curl git tar xz openssl +WORKDIR /server RUN set -eu; \ case "${TARGETARCH:-amd64}" in \ amd64) ZIG_ARCH=x86_64 ;; \ arm64) ZIG_ARCH=aarch64 ;; \ *) echo "unsupported arch: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ - curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" \ + curl -fSL -m ${TIMEOUT_SEC} "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" \ | tar -xJ -C /opt; \ mv "/opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}" /opt/zig ENV PATH="/opt/zig:${PATH}" +COPY build.zig build.zig.zon ./ +COPY src ./src + # Vendor zix X.Y.Z, separate layer so source-only rebuilds skip the fetch. # The Http1 raw engine work this image needs (large-body drain plus the per-worker # response cache used by the /json endpoint) must be present on the X.Y.Z branch. @@ -30,15 +35,15 @@ ENV PATH="/opt/zig:${PATH}" # every attempt. RUN set -eu; \ fetch() { \ - rm -rf /src/vendor/zix; mkdir -p /src/vendor/zix; \ - curl -fsSL --retry ${RETRY} --retry-delay ${RETRY_DELAY} --retry-all-errors "$1" -o /tmp/zix.tar.gz \ + rm -rf /server/vendor/zix; mkdir -p /server/vendor/zix; \ + curl -fSL --retry ${RETRY} --retry-delay ${RETRY_DELAY} --retry-all-errors "$1" -o /tmp/zix.tar.gz \ && tar -xz --strip-components=1 -C /src/vendor/zix -f /tmp/zix.tar.gz; \ }; \ clone() { \ attempt=0; \ while [ "${attempt}" -lt "${RETRY}" ]; do \ - rm -rf /src/vendor/zix; \ - git clone --depth 1 --branch "${ZIX_VERSION}" "$1" /src/vendor/zix && return 0; \ + rm -rf /server/vendor/zix; \ + git clone --depth 1 --branch "${ZIX_VERSION}" "$1" /server/vendor/zix && return 0; \ attempt=$((attempt + 1)); \ sleep "${RETRY_DELAY}"; \ done; \ @@ -53,17 +58,17 @@ RUN set -eu; \ clone "https://codeberg.org/prothegee/zix.git" \ || { echo "FAILED: git clone ${RETRY} times from codeberg" >&2; exit 1; }; }; }; } -WORKDIR /src -COPY build.zig build.zig.zon ./ -COPY src ./src +# +aes+pclmul: x86_64_v3 omits AES-NI / PCLMUL, +# so zix TLS would compile the ~40x slower software AES-GCM. +# +adx speeds the RSA / Montgomery path. Every x86_64_v3 CPU has them, so it is safe. RUN set -eu; \ case "${TARGETARCH:-amd64}" in \ amd64) ZIG_TARGET=x86_64-linux-musl; ZIG_CPU=x86_64_v3 ;; \ arm64) ZIG_TARGET=aarch64-linux-musl; ZIG_CPU=baseline ;; \ esac; \ - zig build -Dtarget="${ZIG_TARGET}" -Dcpu="${ZIG_CPU}" --release=fast + zig build -Dtarget="${ZIG_TARGET}" -Dcpu="${ZIG_CPU}+aes+pclmul+adx" --release=fast FROM alpine:3.20 -COPY --from=build /src/zig-out/bin/zix-ws /zix-ws +COPY --from=build /server/zig-out/bin/zix-ws /zix-ws EXPOSE 8080 ENTRYPOINT ["/zix-ws"] From 4c7f9f953d1edfbbc403fde7e0b9984d2ef0b8b2 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 13 Jul 2026 17:40:07 +0700 Subject: [PATCH 04/13] modify zix-ws entry --- frameworks/zix-ws/src/main.zig | 57 ++++++++++++---------------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/frameworks/zix-ws/src/main.zig b/frameworks/zix-ws/src/main.zig index 05adac321..23cb27ff9 100644 --- a/frameworks/zix-ws/src/main.zig +++ b/frameworks/zix-ws/src/main.zig @@ -1,57 +1,36 @@ //! HttpArena: zix-ws //! -//! zix HttpArena WebSocket entry point. -//! -//! Intent: demonstrate the engine-owned WebSocket path of zix.Http1 (EPOLL -//! dispatch model) against the HttpArena echo and echo-pipeline suites. -//! -//! Design choices: -//! - GET /ws upgrades, then zix.Http1.WebSocket.serve drives the echo loop -//! inside the engine: frames are echoed on readiness and a pipelined burst is -//! coalesced into a single write. -//! - No response cache: echo is per-connection, not a broadcast fanout, so there -//! is nothing to precompute or share across connections. +//! zix.Http1 engine-owned WebSocket (.URING) +//! against the HttpArena echo and echo-pipeline suites. +//! GET /ws upgrades, +//! then WebSocket.serve drives the echo loop inside the engine (frames echoed on +//! readiness, a pipelined burst coalesced into one write). +//! No response cache: echo is per-connection, nothing to precompute or share. const std = @import("std"); const zix = @import("zix"); // --------------------------------------------------------- // +const IP: []const u8 = "::"; const PORT: u16 = 8080; -const LISTEN_IP: []const u8 = "::"; const DISPATCH_MODEL: zix.Http1.DispatchModel = .URING; -const KERNEL_BACKLOG: u31 = 16 * 1024; - -/// Per-machine tuning profile (ADR-041 increment 5): .lean for the 12-thread / -/// 32 GB dev box, .throughput for the 64-core / 251 GB competition box. Only the -/// HTTP handshake recv buffer differs (the WS frame buffer is already 32 KiB). -/// Select .throughput for the 64-core deployment. -const Profile = enum { lean, throughput }; -const PROFILE: Profile = .lean; - -const MAX_RECV_BUF: usize = switch (PROFILE) { - .lean => 4 * 1024, - .throughput => 16 * 1024, -}; -const WS_RECV_BUF: usize = 32 * 1024; -const MAX_HEADERS: u8 = 16; -const WORKERS: usize = 0; // --------------------------------------------------------- // fn badRequest(fd: std.posix.fd_t) void { - zix.Http1.writeSimple(fd, 400, "text/plain", "bad request") catch {}; + zix.Http1.sendSimpleFD(fd, 400, "text/plain", "bad request") catch {}; } fn notFound(fd: std.posix.fd_t) void { - zix.Http1.writeSimple(fd, 404, "text/plain", "Not Found") catch {}; + zix.Http1.sendSimpleFD(fd, 404, "text/plain", "Not Found") catch {}; } // --------------------------------------------------------- // // Echo every text/binary frame back. Ping/close are handled by the engine. fn wsOnFrame(fd: std.posix.fd_t, opcode: u8, payload: []const u8) void { - zix.Http1.WebSocket.send(fd, @enumFromInt(opcode), payload) catch {}; + zix.Http1.WebSocket.sendFD(fd, @enumFromInt(opcode), payload) catch {}; } // GET /ws : WebSocket upgrade then engine-owned echo. @@ -66,7 +45,7 @@ fn wsHandler(head: *const zix.Http1.ParsedHead, body: []const u8, fd: std.posix. } zix.Http1.WebSocket.serve(fd, ws_key.?, wsOnFrame) catch { - zix.Http1.writeSimple(fd, 500, "text/plain", "handshake failed") catch {}; + zix.Http1.sendSimpleFD(fd, 500, "text/plain", "handshake failed") catch {}; return; }; } @@ -88,15 +67,17 @@ pub fn main(process: std.process.Init) !void { var server = zix.Http1.Server.init(dispatch, .{ .io = process.io, - .ip = LISTEN_IP, + .ip = IP, .port = PORT, .dispatch_model = DISPATCH_MODEL, - .kernel_backlog = KERNEL_BACKLOG, - .max_recv_buf = MAX_RECV_BUF, - .ws_recv_buf = WS_RECV_BUF, - .max_headers = MAX_HEADERS, - .workers = WORKERS, + .workers = 0, + .kernel_backlog = 16 * 1024, + .max_recv_buf = 6 * 1024, + .ws_recv_buf = 32 * 1024, + .max_headers = 8, .send_date_header = false, + .uring_send_buf_size = 16 * 1024, + .process_queue_len = 2_000_000, }); defer server.deinit(); From 8cfc0c856cd88692b940ef272db07640143eb187 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 13 Jul 2026 17:47:40 +0700 Subject: [PATCH 05/13] fix error when extract the tar file --- frameworks/zix-ws/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/zix-ws/Dockerfile b/frameworks/zix-ws/Dockerfile index ee2bfc7ca..a9253e7e9 100644 --- a/frameworks/zix-ws/Dockerfile +++ b/frameworks/zix-ws/Dockerfile @@ -37,7 +37,7 @@ RUN set -eu; \ fetch() { \ rm -rf /server/vendor/zix; mkdir -p /server/vendor/zix; \ curl -fSL --retry ${RETRY} --retry-delay ${RETRY_DELAY} --retry-all-errors "$1" -o /tmp/zix.tar.gz \ - && tar -xz --strip-components=1 -C /src/vendor/zix -f /tmp/zix.tar.gz; \ + && tar -xz --strip-components=1 -C /server/vendor/zix -f /tmp/zix.tar.gz; \ }; \ clone() { \ attempt=0; \ From 57f778b71a85bce9ca5e208f27e1e983f6890c2a Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 13 Jul 2026 17:50:40 +0700 Subject: [PATCH 06/13] replace zix version target --- frameworks/zix-ws/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/zix-ws/Dockerfile b/frameworks/zix-ws/Dockerfile index a9253e7e9..c5c70ce0f 100644 --- a/frameworks/zix-ws/Dockerfile +++ b/frameworks/zix-ws/Dockerfile @@ -6,7 +6,7 @@ ARG TARGETARCH ARG RETRY_DELAY=3 ARG TIMEOUT_SEC=180 ARG ZIG_VERSION=0.16.0 -ARG ZIX_VERSION=0.4.x-rc3 +ARG ZIX_VERSION=0.5.x-rc1 RUN apk add --no-cache ca-certificates curl git tar xz openssl WORKDIR /server From 63cef6a176278cbceda5e0c1862d9651d91c3594 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 13 Jul 2026 18:21:31 +0700 Subject: [PATCH 07/13] re-trigger validate ci attempt 1 From 14b9790c0f347646578d985b162d3883d6500715 Mon Sep 17 00:00:00 2001 From: prothegee Date: Sat, 15 Aug 2026 09:00:40 +0700 Subject: [PATCH 08/13] refactor(zix-ws): zig fetch, Router API Dockerfile: - Bump zix version to 0.5.x - Use zig fetch over curl/git - Add github fallback for fetch src/main.zig: - Use zix.Http1.Router API - Move WS logic to handlers/ws - Scale queue length by CPU count - Tune uring idle pool limits meta.json: - Update repo URL to GitHub - Add echo-ws-limited test - Update framework description --- frameworks/zix-ws/Dockerfile | 58 ++++++++-------- frameworks/zix-ws/build.zig.zon.template | 12 ++++ frameworks/zix-ws/meta.json | 11 +-- frameworks/zix-ws/src/handlers/ws.zig | 45 ++++++++++++ frameworks/zix-ws/src/main.zig | 88 +++++++----------------- 5 files changed, 116 insertions(+), 98 deletions(-) create mode 100644 frameworks/zix-ws/build.zig.zon.template create mode 100644 frameworks/zix-ws/src/handlers/ws.zig diff --git a/frameworks/zix-ws/Dockerfile b/frameworks/zix-ws/Dockerfile index c5c70ce0f..7e0ef8154 100644 --- a/frameworks/zix-ws/Dockerfile +++ b/frameworks/zix-ws/Dockerfile @@ -6,7 +6,7 @@ ARG TARGETARCH ARG RETRY_DELAY=3 ARG TIMEOUT_SEC=180 ARG ZIG_VERSION=0.16.0 -ARG ZIX_VERSION=0.5.x-rc1 +ARG ZIX_VERSION=0.5.x RUN apk add --no-cache ca-certificates curl git tar xz openssl WORKDIR /server @@ -21,42 +21,28 @@ RUN set -eu; \ mv "/opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}" /opt/zig ENV PATH="/opt/zig:${PATH}" -COPY build.zig build.zig.zon ./ +COPY build.zig ./ +COPY build.zig.zon.template ./build.zig.zon COPY src ./src -# Vendor zix X.Y.Z, separate layer so source-only rebuilds skip the fetch. -# The Http1 raw engine work this image needs (large-body drain plus the per-worker -# response cache used by the /json endpoint) must be present on the X.Y.Z branch. -# Four ordered attempts before giving up: curl the archive tarball from github then -# codeberg, then a shallow git clone from github then codeberg. The github archive -# redirects to codeload.github.com (which the benchmark runner may not resolve), so -# curl can fall through to the codeberg tarball, and git clone talks to github.com -# and codeberg.org directly as the deeper fallback. RETRY and RETRY_DELAY bound -# every attempt. +# Resolve zix with zig fetch git+https RUN set -eu; \ - fetch() { \ - rm -rf /server/vendor/zix; mkdir -p /server/vendor/zix; \ - curl -fSL --retry ${RETRY} --retry-delay ${RETRY_DELAY} --retry-all-errors "$1" -o /tmp/zix.tar.gz \ - && tar -xz --strip-components=1 -C /server/vendor/zix -f /tmp/zix.tar.gz; \ - }; \ - clone() { \ - attempt=0; \ - while [ "${attempt}" -lt "${RETRY}" ]; do \ - rm -rf /server/vendor/zix; \ - git clone --depth 1 --branch "${ZIX_VERSION}" "$1" /server/vendor/zix && return 0; \ + fetch_zix() { \ + url="$1"; attempt=1; \ + while [ "${attempt}" -le "${RETRY}" ]; do \ + if $(zig fetch --save ${url}); then \ + return 0; \ + fi; \ + echo "zix: ${url} attempt ${attempt}/${RETRY} failed" >&2; \ attempt=$((attempt + 1)); \ - sleep "${RETRY_DELAY}"; \ + [ "${attempt}" -le "${RETRY}" ] && sleep "${RETRY_DELAY}"; \ done; \ return 1; \ }; \ - fetch "https://github.com/prothegee/zix/archive/refs/heads/${ZIX_VERSION}.tar.gz" \ - || { echo "FAILED: curl ${RETRY} times from github" >&2; \ - fetch "https://codeberg.org/prothegee/zix/archive/${ZIX_VERSION}.tar.gz" \ - || { echo "FAILED: curl ${RETRY} times from codeberg" >&2; \ - clone "https://github.com/prothegee/zix.git" \ - || { echo "FAILED: git clone ${RETRY} times from github" >&2; \ - clone "https://codeberg.org/prothegee/zix.git" \ - || { echo "FAILED: git clone ${RETRY} times from codeberg" >&2; exit 1; }; }; }; } + fetch_zix "git+https://codeberg.org/prothegee/zix#${ZIX_VERSION}" \ + || { echo "zix: codeberg exhausted ${RETRY} attempts" >&2; \ + fetch_zix "git+https://github.com/prothegee/zix#${ZIX_VERSION}"; } \ + || { echo "zix: github exhausted ${RETRY} attempts" >&2; exit 1; } # +aes+pclmul: x86_64_v3 omits AES-NI / PCLMUL, # so zix TLS would compile the ~40x slower software AES-GCM. @@ -66,7 +52,17 @@ RUN set -eu; \ amd64) ZIG_TARGET=x86_64-linux-musl; ZIG_CPU=x86_64_v3 ;; \ arm64) ZIG_TARGET=aarch64-linux-musl; ZIG_CPU=baseline ;; \ esac; \ - zig build -Dtarget="${ZIG_TARGET}" -Dcpu="${ZIG_CPU}+aes+pclmul+adx" --release=fast + zig build \ + -Dtarget="${ZIG_TARGET}" \ + -Dcpu="${ZIG_CPU}+aes+pclmul+adx" \ + --release=fast --summary failures; + +# Self-signed Ed25519 cert generated at image build, baked at /etc/zix-tls. +RUN set -eu; \ + mkdir -p /etc/zix-tls; \ + openssl genpkey -algorithm ED25519 -out /etc/zix-tls/server.key; \ + openssl req -new -x509 -key /etc/zix-tls/server.key -out /etc/zix-tls/server.cert \ + -days 3650 -subj "/CN=localhost" FROM alpine:3.20 COPY --from=build /server/zig-out/bin/zix-ws /zix-ws diff --git a/frameworks/zix-ws/build.zig.zon.template b/frameworks/zix-ws/build.zig.zon.template new file mode 100644 index 000000000..a17a2a900 --- /dev/null +++ b/frameworks/zix-ws/build.zig.zon.template @@ -0,0 +1,12 @@ +.{ + .name = .zix_ws_arena, + .version = "0.1.0", + .fingerprint = 0xba3a8c57d2738130, + .minimum_zig_version = "0.16.0", + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, + .dependencies = .{}, +} diff --git a/frameworks/zix-ws/meta.json b/frameworks/zix-ws/meta.json index c091b5486..4f04e4950 100644 --- a/frameworks/zix-ws/meta.json +++ b/frameworks/zix-ws/meta.json @@ -3,12 +3,15 @@ "language": "Zig", "type": "engine", "engine": "zix.Http1 WebSocket URING Dispatch Model", - "description": "Zig WebSocket echo on the zix.Http1 engine-owned WebSocket path, .URING dispatch: GET /ws upgrades, WebSocket.serve echoes inside the engine, a pipelined burst coalesced into one write.", - "repo": "https://codeberg.org/prothegee/zix", + "description": "WebSocket echo on the zix.Http1 engine, .URING dispatch. GET /ws upgrades and the engine drives the frame pump: frames are echoed on readiness and a pipelined burst leaves as one write. No cache anywhere, echo is per connection.", + "repo": "https://github.com/prothegee/zix", "enabled": true, "tests": [ "echo-ws", - "echo-ws-pipeline" + "echo-ws-pipeline", + "echo-ws-limited" ], - "maintainers": ["prothegee"] + "maintainers": [ + "prothegee" + ] } diff --git a/frameworks/zix-ws/src/handlers/ws.zig b/frameworks/zix-ws/src/handlers/ws.zig new file mode 100644 index 000000000..b5e64d463 --- /dev/null +++ b/frameworks/zix-ws/src/handlers/ws.zig @@ -0,0 +1,45 @@ +//! GET /ws : upgrade the connection, then hand it to the engine's echo loop. +//! +//! Note: +//! - Nothing is cached. Echo is per connection, so there is nothing to share +//! between them. + +const std = @import("std"); +const zix = @import("zix"); + +// --------------------------------------------------------- // + +pub const PATH = "/ws"; + +// --------------------------------------------------------- // + +/// Echo one text or binary frame back to its sender. +/// +/// Note: +/// - Ping and close are answered by the engine before this runs. +/// - The frame callback returns void, so a failed echo has nowhere to report: +/// the handler has long returned by the time frames arrive. A broken send +/// here means the peer is gone, which the frame loop detects on its own read. +fn onFrame(fd: std.posix.fd_t, opcode: u8, payload: []const u8) void { + zix.Http1.WebSocket.sendFD(fd, @enumFromInt(opcode), payload) catch {}; +} + +// --------------------------------------------------------- // + +pub fn RESPONSE(req: *zix.Http1.Request, res: *zix.Http1.Response, _: *zix.Http1.Context) !void { + const upgrade = req.header("upgrade") orelse ""; + const key = req.header("sec-websocket-key"); + + if (!std.ascii.eqlIgnoreCase(upgrade, "websocket") or key == null) { + res.setStatus(.BAD_REQUEST); + + try res.send("not a websocket upgrade request"); + return; + } + + // Reported rather than answered here: the upgrade writes the 101 itself, so + // a failure partway through has already put bytes on the wire and an HTTP + // body sent after it would corrupt the stream. The engine sends its 500 + // only while nothing has been written. + try zix.Http1.WebSocket.serve(req.fd, key.?, onFrame); +} diff --git a/frameworks/zix-ws/src/main.zig b/frameworks/zix-ws/src/main.zig index 23cb27ff9..6a4a3b3fa 100644 --- a/frameworks/zix-ws/src/main.zig +++ b/frameworks/zix-ws/src/main.zig @@ -1,83 +1,45 @@ -//! HttpArena: zix-ws +//! localbench: zix-ws //! -//! zix.Http1 engine-owned WebSocket (.URING) -//! against the HttpArena echo and echo-pipeline suites. -//! GET /ws upgrades, -//! then WebSocket.serve drives the echo loop inside the engine (frames echoed on -//! readiness, a pipelined burst coalesced into one write). -//! No response cache: echo is per-connection, nothing to precompute or share. +//! zix.Http1 WebSocket (.URING), Router-only: one handler module per route +//! (src/handlers/). GET /ws upgrades, then the engine drives the echo loop: +//! frames are echoed on readiness and a pipelined burst is coalesced into one +//! write. const std = @import("std"); const zix = @import("zix"); -// --------------------------------------------------------- // - -const IP: []const u8 = "::"; -const PORT: u16 = 8080; -const DISPATCH_MODEL: zix.Http1.DispatchModel = .URING; - -// --------------------------------------------------------- // - -fn badRequest(fd: std.posix.fd_t) void { - zix.Http1.sendSimpleFD(fd, 400, "text/plain", "bad request") catch {}; -} - -fn notFound(fd: std.posix.fd_t) void { - zix.Http1.sendSimpleFD(fd, 404, "text/plain", "Not Found") catch {}; -} +const ws = @import("handlers/ws.zig"); // --------------------------------------------------------- // -// Echo every text/binary frame back. Ping/close are handled by the engine. -fn wsOnFrame(fd: std.posix.fd_t, opcode: u8, payload: []const u8) void { - zix.Http1.WebSocket.sendFD(fd, @enumFromInt(opcode), payload) catch {}; -} - -// GET /ws : WebSocket upgrade then engine-owned echo. -fn wsHandler(head: *const zix.Http1.ParsedHead, body: []const u8, fd: std.posix.fd_t) void { - _ = body; - - const upgrade_val = zix.Http1.getHeader(head, "upgrade") orelse ""; - const ws_key = zix.Http1.getHeader(head, "sec-websocket-key"); - - if (!std.ascii.eqlIgnoreCase(upgrade_val, "websocket") or ws_key == null) { - return badRequest(fd); - } - - zix.Http1.WebSocket.serve(fd, ws_key.?, wsOnFrame) catch { - zix.Http1.sendSimpleFD(fd, 500, "text/plain", "handshake failed") catch {}; - return; - }; -} - -// --------------------------------------------------------- // - -fn dispatch(head: *const zix.Http1.ParsedHead, body: []const u8, fd: std.posix.fd_t) void { - if (std.mem.eql(u8, head.path, "/ws")) return wsHandler(head, body, fd); - - notFound(fd); -} - -// --------------------------------------------------------- // +const Routes = zix.Http1.Router(&[_]zix.Http1.Route{ + .{ .path = ws.PATH, .handler = ws.RESPONSE }, +}); pub fn main(process: std.process.Init) !void { - // Elevate scheduling priority (setpriority -19). Fails silently when the - // process lacks CAP_SYS_NICE, so no special capability is required for correctness. - _ = std.os.linux.syscall3(.setpriority, 0, 0, @as(usize, @bitCast(@as(isize, -19)))); + // Park ring sized to peak conns per worker: 16384c is the deepest + // scenario and workers = 0 spawns one worker per CPU. + const cpus = std.Thread.getCpuCount() catch 8; + const park_len = @max(512, 16 * 1024 / cpus); - var server = zix.Http1.Server.init(dispatch, .{ + var server = zix.Http1.Server.init(Routes.dispatch, .{ .io = process.io, - .ip = IP, - .port = PORT, - .dispatch_model = DISPATCH_MODEL, + .ip = "::", + .port = 8080, .workers = 0, + .dispatch_model = .URING, + // + .send_date_header = false, + .max_response_headers = .{ .CUSTOM = 8 }, + // .kernel_backlog = 16 * 1024, .max_recv_buf = 6 * 1024, .ws_recv_buf = 32 * 1024, - .max_headers = 8, - .send_date_header = false, + // .uring_send_buf_size = 16 * 1024, - .process_queue_len = 2_000_000, + .uring_idle_pool_floor = 16, + .uring_idle_pool_ceiling = 1 * 1024, + .process_queue_len = park_len, }); defer server.deinit(); From 733d2a789a9e94f782b759873d2f73676ad83492 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 15 Aug 2026 08:00:16 +0000 Subject: [PATCH 09/13] Benchmark results: zix-ws [skip ci] --- site/data/current.json | 2 +- site/data/frameworks.json | 14 +- site/data/results/zix.json | 152 ++++++++++++++++++ .../logs/echo-ws-limited/4096/zix-ws.log | 1 + .../logs/echo-ws-limited/512/zix-ws.log | 1 + .../logs/echo-ws-pipeline/16384/zix-ws.log | 1 + .../logs/echo-ws-pipeline/4096/zix-ws.log | 1 + .../logs/echo-ws-pipeline/512/zix-ws.log | 1 + site/static/logs/echo-ws/16384/zix-ws.log | 1 + site/static/logs/echo-ws/4096/zix-ws.log | 1 + site/static/logs/echo-ws/512/zix-ws.log | 1 + 11 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 site/static/logs/echo-ws-limited/4096/zix-ws.log create mode 100644 site/static/logs/echo-ws-limited/512/zix-ws.log diff --git a/site/data/current.json b/site/data/current.json index 46e72dfff..f8d0404a9 100644 --- a/site/data/current.json +++ b/site/data/current.json @@ -8,7 +8,7 @@ "kernel": "6.17.0-22-generic", "docker": "29.3.0", "docker_runtime": "runc", - "governor": "powersave", + "governor": "performance", "tcp": { "lo_mtu": "1500", "congestion": "cubic", diff --git a/site/data/frameworks.json b/site/data/frameworks.json index 52b0d818f..12b856b27 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -1209,14 +1209,14 @@ "repo": "https://github.com/prothegee/zix", "type": "engine", "engine": "zix.Http3 URING Dispatch Model" + }, + { + "dir": "zix-ws", + "description": "WebSocket echo on the zix.Http1 engine, .URING dispatch. GET /ws upgrades and the engine drives the frame pump: frames are echoed on readiness and a pipelined burst leaves as one write. No cache anywhere, echo is per connection.", + "repo": "https://github.com/prothegee/zix", + "type": "engine", + "engine": "zix.Http1 WebSocket URING Dispatch Model" } ] - }, - "zix-ws": { - "dir": "zix-ws", - "description": "Zig WebSocket echo server on the zix.Http1 raw engine (no std.http). Shared-nothing by design: each worker owns its own SO_REUSEPORT multishot accept, io_uring completion ring, and connections, with no shared state or locking across cores. The upgrade and echo are engine-owned: frames are read from a shared per-worker provided-buffer ring (an idle connection holds no buffer), and a pipelined burst is coalesced into one write.", - "repo": "https://codeberg.org/prothegee/zix", - "type": "engine", - "engine": "zix" } } \ No newline at end of file diff --git a/site/data/results/zix.json b/site/data/results/zix.json index 67822789b..bf08a7d03 100644 --- a/site/data/results/zix.json +++ b/site/data/results/zix.json @@ -60,6 +60,158 @@ "status_4xx": 0, "status_5xx": 0 }, + "echo-ws-16384": { + "framework": "zix", + "language": "Zig", + "rps": 4296243, + "avg_latency": "3.79ms", + "p99_latency": "4.41ms", + "cpu": "6118.7%", + "memory": "499MiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "29.06MB/s", + "reconnects": 0, + "status_2xx": 21481215, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "echo-ws-4096": { + "framework": "zix", + "language": "Zig", + "rps": 4607533, + "avg_latency": "888us", + "p99_latency": "1.18ms", + "cpu": "6411.3%", + "memory": "255MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "30.75MB/s", + "reconnects": 0, + "status_2xx": 23037667, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "echo-ws-512": { + "framework": "zix", + "language": "Zig", + "rps": 4361456, + "avg_latency": "116us", + "p99_latency": "209us", + "cpu": "6407.8%", + "memory": "207MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "29.11MB/s", + "reconnects": 0, + "status_2xx": 21807284, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "echo-ws-limited-4096": { + "framework": "zix", + "language": "Zig", + "rps": 2579824, + "avg_latency": "1.02ms", + "p99_latency": "1.75ms", + "cpu": "5878.9%", + "memory": "282MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "48.98MB/s", + "reconnects": 1290835, + "status_2xx": 12899121, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "echo-ws-limited-512": { + "framework": "zix", + "language": "Zig", + "rps": 2260961, + "avg_latency": "115us", + "p99_latency": "340us", + "cpu": "5225.9%", + "memory": "178MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "42.90MB/s", + "reconnects": 1130532, + "status_2xx": 11304807, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "echo-ws-pipeline-16384": { + "framework": "zix", + "language": "Zig", + "rps": 55172806, + "avg_latency": "4.73ms", + "p99_latency": "8.82ms", + "cpu": "6399.5%", + "memory": "500MiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "368.57MB/s", + "reconnects": 0, + "status_2xx": 275864034, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "echo-ws-pipeline-4096": { + "framework": "zix", + "language": "Zig", + "rps": 67064433, + "avg_latency": "974us", + "p99_latency": "2.03ms", + "cpu": "6176.8%", + "memory": "267MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "447.52MB/s", + "reconnects": 0, + "status_2xx": 335322166, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "echo-ws-pipeline-512": { + "framework": "zix", + "language": "Zig", + "rps": 60350513, + "avg_latency": "135us", + "p99_latency": "304us", + "cpu": "6361.1%", + "memory": "212MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "402.82MB/s", + "reconnects": 0, + "status_2xx": 301752567, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, "json-4096": { "framework": "zix", "language": "Zig", diff --git a/site/static/logs/echo-ws-limited/4096/zix-ws.log b/site/static/logs/echo-ws-limited/4096/zix-ws.log new file mode 100644 index 000000000..35745b8ed --- /dev/null +++ b/site/static/logs/echo-ws-limited/4096/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) diff --git a/site/static/logs/echo-ws-limited/512/zix-ws.log b/site/static/logs/echo-ws-limited/512/zix-ws.log new file mode 100644 index 000000000..35745b8ed --- /dev/null +++ b/site/static/logs/echo-ws-limited/512/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) diff --git a/site/static/logs/echo-ws-pipeline/16384/zix-ws.log b/site/static/logs/echo-ws-pipeline/16384/zix-ws.log index e69de29bb..35745b8ed 100644 --- a/site/static/logs/echo-ws-pipeline/16384/zix-ws.log +++ b/site/static/logs/echo-ws-pipeline/16384/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) diff --git a/site/static/logs/echo-ws-pipeline/4096/zix-ws.log b/site/static/logs/echo-ws-pipeline/4096/zix-ws.log index e69de29bb..35745b8ed 100644 --- a/site/static/logs/echo-ws-pipeline/4096/zix-ws.log +++ b/site/static/logs/echo-ws-pipeline/4096/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) diff --git a/site/static/logs/echo-ws-pipeline/512/zix-ws.log b/site/static/logs/echo-ws-pipeline/512/zix-ws.log index e69de29bb..35745b8ed 100644 --- a/site/static/logs/echo-ws-pipeline/512/zix-ws.log +++ b/site/static/logs/echo-ws-pipeline/512/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) diff --git a/site/static/logs/echo-ws/16384/zix-ws.log b/site/static/logs/echo-ws/16384/zix-ws.log index e69de29bb..35745b8ed 100644 --- a/site/static/logs/echo-ws/16384/zix-ws.log +++ b/site/static/logs/echo-ws/16384/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) diff --git a/site/static/logs/echo-ws/4096/zix-ws.log b/site/static/logs/echo-ws/4096/zix-ws.log index e69de29bb..35745b8ed 100644 --- a/site/static/logs/echo-ws/4096/zix-ws.log +++ b/site/static/logs/echo-ws/4096/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) diff --git a/site/static/logs/echo-ws/512/zix-ws.log b/site/static/logs/echo-ws/512/zix-ws.log index e69de29bb..35745b8ed 100644 --- a/site/static/logs/echo-ws/512/zix-ws.log +++ b/site/static/logs/echo-ws/512/zix-ws.log @@ -0,0 +1 @@ +info(zix_http1): listening on :::8080 (io_uring, 64 workers, shared-nothing) From 66dfb217f59f38a98728a09ddf2e28128dc1b9f4 Mon Sep 17 00:00:00 2001 From: prothegee Date: Sat, 15 Aug 2026 19:28:58 +0700 Subject: [PATCH 10/13] sync: zix-ws 20260815 From f2edede5a7b61b5a4932a7c94a0d682e4605c5bc Mon Sep 17 00:00:00 2001 From: prothegee Date: Sat, 15 Aug 2026 19:54:25 +0700 Subject: [PATCH 11/13] replace head comment title --- frameworks/zix-ws/src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/zix-ws/src/main.zig b/frameworks/zix-ws/src/main.zig index 6a4a3b3fa..71d1e4287 100644 --- a/frameworks/zix-ws/src/main.zig +++ b/frameworks/zix-ws/src/main.zig @@ -1,4 +1,4 @@ -//! localbench: zix-ws +//! zix-ws //! //! zix.Http1 WebSocket (.URING), Router-only: one handler module per route //! (src/handlers/). GET /ws upgrades, then the engine drives the echo loop: From 3f21d35e582f03492fa84d3b4db817cae7c4f83f Mon Sep 17 00:00:00 2001 From: prothegee Date: Sat, 15 Aug 2026 19:54:43 +0700 Subject: [PATCH 12/13] adding zig suffix but in current --- frameworks/zix-ws/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/frameworks/zix-ws/.gitignore b/frameworks/zix-ws/.gitignore index 595d20d1a..0e6a877fe 100644 --- a/frameworks/zix-ws/.gitignore +++ b/frameworks/zix-ws/.gitignore @@ -2,3 +2,4 @@ zig-out zig-package vendor +/zig* From e03c4afb4816534a7dada21e7b36f9a829194225 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Aug 2026 08:44:39 +0000 Subject: [PATCH 13/13] Benchmark results: zix-ws [skip ci] --- site/data/results/zix.json | 112 ++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/site/data/results/zix.json b/site/data/results/zix.json index 3cbf66844..41f4e75b0 100644 --- a/site/data/results/zix.json +++ b/site/data/results/zix.json @@ -136,18 +136,18 @@ "echo-ws-16384": { "framework": "zix", "language": "Zig", - "rps": 4296243, - "avg_latency": "3.79ms", - "p99_latency": "4.41ms", - "cpu": "6118.7%", - "memory": "499MiB", + "rps": 4294811, + "avg_latency": "3.80ms", + "p99_latency": "4.49ms", + "cpu": "6171.1%", + "memory": "498MiB", "connections": 16384, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "29.06MB/s", + "bandwidth": "29.05MB/s", "reconnects": 0, - "status_2xx": 21481215, + "status_2xx": 21474059, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -155,18 +155,18 @@ "echo-ws-4096": { "framework": "zix", "language": "Zig", - "rps": 4607533, - "avg_latency": "888us", - "p99_latency": "1.18ms", - "cpu": "6411.3%", - "memory": "255MiB", + "rps": 4575698, + "avg_latency": "894us", + "p99_latency": "1.25ms", + "cpu": "6268.9%", + "memory": "248MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "30.75MB/s", + "bandwidth": "30.53MB/s", "reconnects": 0, - "status_2xx": 23037667, + "status_2xx": 22878492, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -174,18 +174,18 @@ "echo-ws-512": { "framework": "zix", "language": "Zig", - "rps": 4361456, - "avg_latency": "116us", - "p99_latency": "209us", - "cpu": "6407.8%", - "memory": "207MiB", + "rps": 4393637, + "avg_latency": "115us", + "p99_latency": "210us", + "cpu": "6426.5%", + "memory": "208MiB", "connections": 512, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "29.11MB/s", + "bandwidth": "29.32MB/s", "reconnects": 0, - "status_2xx": 21807284, + "status_2xx": 21968189, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -193,18 +193,18 @@ "echo-ws-limited-4096": { "framework": "zix", "language": "Zig", - "rps": 2579824, + "rps": 2600418, "avg_latency": "1.02ms", - "p99_latency": "1.75ms", - "cpu": "5878.9%", - "memory": "282MiB", + "p99_latency": "1.74ms", + "cpu": "5889.4%", + "memory": "284MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "48.98MB/s", - "reconnects": 1290835, - "status_2xx": 12899121, + "bandwidth": "49.36MB/s", + "reconnects": 1300814, + "status_2xx": 13002091, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -212,18 +212,18 @@ "echo-ws-limited-512": { "framework": "zix", "language": "Zig", - "rps": 2260961, - "avg_latency": "115us", - "p99_latency": "340us", - "cpu": "5225.9%", - "memory": "178MiB", + "rps": 2263092, + "avg_latency": "114us", + "p99_latency": "345us", + "cpu": "5345.9%", + "memory": "176MiB", "connections": 512, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "42.90MB/s", - "reconnects": 1130532, - "status_2xx": 11304807, + "bandwidth": "42.94MB/s", + "reconnects": 1131608, + "status_2xx": 11315464, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -231,18 +231,18 @@ "echo-ws-pipeline-16384": { "framework": "zix", "language": "Zig", - "rps": 55172806, - "avg_latency": "4.73ms", - "p99_latency": "8.82ms", - "cpu": "6399.5%", - "memory": "500MiB", + "rps": 61585277, + "avg_latency": "4.22ms", + "p99_latency": "8.42ms", + "cpu": "6117.6%", + "memory": "503MiB", "connections": 16384, "threads": 64, "duration": "5s", "pipeline": 16, - "bandwidth": "368.57MB/s", + "bandwidth": "411.40MB/s", "reconnects": 0, - "status_2xx": 275864034, + "status_2xx": 307926388, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -250,18 +250,18 @@ "echo-ws-pipeline-4096": { "framework": "zix", "language": "Zig", - "rps": 67064433, - "avg_latency": "974us", + "rps": 67275748, + "avg_latency": "972us", "p99_latency": "2.03ms", - "cpu": "6176.8%", - "memory": "267MiB", + "cpu": "6428.3%", + "memory": "252MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 16, - "bandwidth": "447.52MB/s", + "bandwidth": "448.87MB/s", "reconnects": 0, - "status_2xx": 335322166, + "status_2xx": 336378744, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -269,18 +269,18 @@ "echo-ws-pipeline-512": { "framework": "zix", "language": "Zig", - "rps": 60350513, - "avg_latency": "135us", - "p99_latency": "304us", - "cpu": "6361.1%", - "memory": "212MiB", + "rps": 61158425, + "avg_latency": "133us", + "p99_latency": "292us", + "cpu": "6247.8%", + "memory": "210MiB", "connections": 512, "threads": 64, "duration": "5s", "pipeline": 16, - "bandwidth": "402.82MB/s", + "bandwidth": "408.11MB/s", "reconnects": 0, - "status_2xx": 301752567, + "status_2xx": 305792126, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0