From 2c967e11035971a18888ee89833fd0a1e5ae621f Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 29 Jun 2026 06:34:40 +0700 Subject: [PATCH 1/6] zix 0.5.x-rc1 --- frameworks/zix-grpc/.gitignore | 4 ++ frameworks/zix-grpc/Dockerfile | 28 ++++++------ frameworks/zix-grpc/meta.json | 10 +++-- frameworks/zix-grpc/src/main.zig | 73 +++++++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 27 deletions(-) create mode 100644 frameworks/zix-grpc/.gitignore diff --git a/frameworks/zix-grpc/.gitignore b/frameworks/zix-grpc/.gitignore new file mode 100644 index 000000000..595d20d1a --- /dev/null +++ b/frameworks/zix-grpc/.gitignore @@ -0,0 +1,4 @@ +.zig-cache +zig-out +zig-package +vendor diff --git a/frameworks/zix-grpc/Dockerfile b/frameworks/zix-grpc/Dockerfile index 0a095f82d..44b290800 100644 --- a/frameworks/zix-grpc/Dockerfile +++ b/frameworks/zix-grpc/Dockerfile @@ -5,8 +5,8 @@ ARG RETRY=6 ARG TARGETARCH ARG RETRY_DELAY=3 ARG ZIG_VERSION=0.16.0 -ARG ZIX_VERSION=0.4.x-rc3 -RUN apk add --no-cache ca-certificates curl git tar xz +ARG ZIX_VERSION=0.5.x-rc1 +RUN apk add --no-cache ca-certificates curl git tar xz openssl RUN set -eu; \ case "${TARGETARCH:-amd64}" in \ @@ -19,15 +19,11 @@ RUN set -eu; \ mv "/opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}" /opt/zig ENV PATH="/opt/zig:${PATH}" -# 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. +# Vendor zix ${ZIX_VERSION} in its own layer so source-only rebuilds skip the fetch. Tries curl +# tarball then git clone, github before codeberg. RETRY / RETRY_DELAY bound every attempt. +# +# Add +aes+pclmul: x86_64_v3 omits AES-NI / PCLMUL, so zix TLS would compile the ~40x slower +# software AES-GCM. Every x86_64_v3 CPU has them, so it is safe. RUN set -eu; \ fetch() { \ rm -rf /src/vendor/zix; mkdir -p /src/vendor/zix; \ @@ -61,9 +57,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}" --release=fast + zig build -Dtarget="${ZIG_TARGET}" -Dcpu="${ZIG_CPU}+aes+pclmul+adx" --release=fast + +# Self-signed Ed25519 cert generated at image build, baked at /etc/zix-tls. Ed25519 handshake +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.crt \ + -days 3650 -subj "/CN=localhost" FROM alpine:3.20 COPY --from=build /src/zig-out/bin/zix-grpc /zix-grpc +COPY --from=build /etc/zix-tls /etc/zix-tls EXPOSE 8080 ENTRYPOINT ["/zix-grpc"] diff --git a/frameworks/zix-grpc/meta.json b/frameworks/zix-grpc/meta.json index 4aac2379e..256a06215 100644 --- a/frameworks/zix-grpc/meta.json +++ b/frameworks/zix-grpc/meta.json @@ -2,13 +2,15 @@ "display_name": "zix-grpc", "language": "Zig", "type": "engine", - "engine": "zix", - "description": "Zig gRPC server (h2c) on the zix.Grpc engine built on zix.Http2 (no std.http). Shared-nothing by design: each worker owns its own SO_REUSEPORT listener, io_uring completion ring, and connections, with no shared state or locking across cores, multiplexing HTTP/2 streams per connection. Replies use comptime-cached HPACK blocks. Implements GetSum (unary) and StreamSum (server-streaming).", - "repo": "https://codeberg.org/prothegee/zix", + "engine": "zix uring", + "description": "Zig gRPC server on the zix.Grpc engine (built on zix.Http2, no std.http). Shared-nothing: each worker owns its SO_REUSEPORT listener, io_uring loop, and connections, multiplexing HTTP/2 streams per connection with comptime-cached HPACK replies. h2c runs under the .URING model. Over TLS 1.3 (ALPN h2, baked self-signed Ed25519 cert) the per-core tls_mux terminates TLS in place. Implements GetSum (unary) and StreamSum (server-streaming).", + "repo": "https://github.com/prothegee/zix", "enabled": true, "tests": [ "unary-grpc", - "stream-grpc" + "stream-grpc", + "unary-grpc-tls", + "stream-grpc-tls" ], "maintainers": ["prothegee"] } diff --git a/frameworks/zix-grpc/src/main.zig b/frameworks/zix-grpc/src/main.zig index 3c8305303..cb9916142 100644 --- a/frameworks/zix-grpc/src/main.zig +++ b/frameworks/zix-grpc/src/main.zig @@ -1,9 +1,16 @@ //! HttpArena: zix-grpc //! -//! zix HttpArena gRPC (h2c) entry point. +//! zix HttpArena gRPC entry point. //! -//! Intent: demonstrate zix.Grpc (EPOLL dispatch model) against the HttpArena -//! gRPC benchmark suite (unary, server-streaming). +//! Intent: demonstrate zix.Grpc (URING dispatch model) against the HttpArena +//! gRPC benchmark suite (unary, server-streaming), cleartext h2c and over TLS. +//! +//! Two listeners run in parallel: +//! - h2c cleartext on PORT (8080) under the .URING dispatch model. Serves unary-grpc and +//! stream-grpc. +//! - gRPC over TLS 1.3 on H2_TLS_PORT (8443), ALPN h2, with a self-signed Ed25519 cert baked at +//! /etc/zix-tls. Serves unary-grpc-tls and stream-grpc-tls. The TLS path is the per-core tls_mux +//! (one worker per core, no thread-per-connection), shared by the .EPOLL and .URING models. //! //! Design choices: //! - GetSum: unary SumRequest{a, b} -> SumReply{a + b}. The compute is a single @@ -18,16 +25,21 @@ const zix = @import("zix"); // --------------------------------------------------------- // const PORT: u16 = 8080; +const H2_TLS_PORT: u16 = 8443; /// Required for ipv4 and ipv6 const LISTEN_IP: []const u8 = "::"; const DISPATCH_MODEL: zix.Grpc.DispatchModel = .URING; const KERNEL_BACKLOG: u31 = 1024 * 16; const WORKERS: usize = 0; -/// 0 selects the engine default EPOLL pool (max(10, cpu*2)). Each worker owns one connection -/// while it is active. After the Phase 1 syscall cuts the unary path is CPU-bound, not -/// connection-bound: a modest cpu-relative pool tops out throughput, while an oversized pool -/// (thread-per-connection) thrashes the scheduler and collapses it. So keep the default. +// TLS cert / key, a self-signed Ed25519 pair baked at /etc/zix-tls at image build. Overridable via env so the same +// binary runs locally. +const TLS_CERT_DEFAULT: []const u8 = "/etc/zix-tls/server.crt"; +const TLS_KEY_DEFAULT: []const u8 = "/etc/zix-tls/server.key"; + +/// Per-core worker count for .URING (this entry's model) and .EPOLL. 0 selects one worker per CPU, +/// which the shared-nothing io_uring loop wants: the unary path is CPU-bound, so a per-core count +/// tops out throughput while oversubscription only thrashes the scheduler. Keep the default. const POOL_SIZE: usize = 0; /// Advertise enough concurrent streams that a client opening many in parallel (h2load uses @@ -108,11 +120,50 @@ fn streamSumHandler(headers: []const zix.Http2.Header, ctx: *zix.Grpc.Context) v // --------------------------------------------------------- // +const ROUTES = &[_]zix.Grpc.Route{ + .{ .path = "/benchmark.BenchmarkService/GetSum", .handler = getSumHandler }, + .{ .path = "/benchmark.BenchmarkService/StreamSum", .handler = streamSumHandler, .is_server_streaming = true }, +}; + +// gRPC over TLS 1.3 listener: the per-core tls_mux terminates TLS (ALPN h2) in place (one worker per +// core, no thread-per-connection). A missing or unreadable cert degrades gracefully: this thread +// returns and the cleartext server keeps running. +fn tlsServer(io: std.Io, tls: *zix.Tls.Context) void { + var server = zix.Grpc.Server.init(ROUTES, .{ + .io = io, + .ip = LISTEN_IP, + .port = H2_TLS_PORT, + .tls = tls, + .dispatch_model = DISPATCH_MODEL, + .kernel_backlog = KERNEL_BACKLOG, + .max_streams = MAX_STREAMS, + .max_body = MAX_BODY, + }) catch return; + defer server.deinit(); + + server.run() catch {}; +} + +// --------------------------------------------------------- // + pub fn main(process: std.process.Init) !void { - var server = try zix.Grpc.Server.init(&[_]zix.Grpc.Route{ - .{ .path = "/benchmark.BenchmarkService/GetSum", .handler = getSumHandler }, - .{ .path = "/benchmark.BenchmarkService/StreamSum", .handler = streamSumHandler, .is_server_streaming = true }, - }, .{ + // gRPC over TLS on H2_TLS_PORT (ALPN h2) with the baked Ed25519 cert. The cleartext server runs + // regardless: a missing cert just leaves the TLS port without a listener. + const cert_path = process.environ_map.get("ARENA_TLS_CERT") orelse TLS_CERT_DEFAULT; + const key_path = process.environ_map.get("ARENA_TLS_KEY") orelse TLS_KEY_DEFAULT; + + var tls_ctx: ?zix.Tls.Context = zix.Tls.Context.init(std.heap.smp_allocator, process.io, .{ + .cert_path = cert_path, + .key_path = key_path, + .alpn = &.{.H2}, + .min_version = .TLS_1_3, + }) catch null; + + if (tls_ctx) |*tls| { + _ = std.Thread.spawn(.{}, tlsServer, .{ process.io, tls }) catch {}; + } + + var server = try zix.Grpc.Server.init(ROUTES, .{ .io = process.io, .ip = LISTEN_IP, .port = PORT, From 65b7f4be677da13ea9068d68b6fd4f46d545fe90 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 29 Jun 2026 07:05:56 +0700 Subject: [PATCH 2/6] retrigger validate 1 From 2752b35773f0d168d37d814eb7b4527cb7e32dda Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 29 Jun 2026 07:08:27 +0700 Subject: [PATCH 3/6] retrigger validate 2 From d877c262bccd1d245f03446733cb46453f8b9da2 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 29 Jun 2026 07:12:39 +0700 Subject: [PATCH 4/6] retrigger validate 3 From 67ca2286e567632a91d13016860c8a447027d1b8 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 29 Jun 2026 20:44:45 +0700 Subject: [PATCH 5/6] re-validate zix-grpc entry From a49579d06e6b5dc4d715edd80e1e097a5ca26ec5 Mon Sep 17 00:00:00 2001 From: prothegee Date: Mon, 29 Jun 2026 20:49:12 +0700 Subject: [PATCH 6/6] re-validate attempt 1