From 4a236716fc9e5eb3eec4db3c7287141b97a0e484 Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Fri, 3 Apr 2026 03:32:20 -0500 Subject: [PATCH] fix evented GPF: ReleaseSafe optimizer bug, build with ReleaseFast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Io.Uring fiber context-switch GPFs under ReleaseSafe on x86_64-linux. Reproduced with a 30-line minimal program (repro_evented.zig): - Debug (safety ON, opts OFF): passes - ReleaseFast (safety OFF, opts ON): passes - ReleaseSmall (safety OFF, opts ON): passes - ReleaseSafe (safety ON, opts ON): GPF in fiber.zig contextSwitch This is a zig 0.16-dev stdlib bug (confirmed in both dev.3059 and dev.3066) — the LLVM optimizer miscompiles safety-checked code in the fiber context switch path. Fix: build with ReleaseFast instead of ReleaseSafe. Backend stays on Io.Evented (fibers on io_uring), eliminating thread-per-PDS. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 4 ++- repro_evented.zig | 70 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 4 +++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 repro_evented.zig diff --git a/Dockerfile b/Dockerfile index 5e3f44e..8ff2484 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,9 @@ RUN zig build --fetch-only 2>/dev/null || true # then copy source and build COPY src/ src/ -RUN zig build -Doptimize=ReleaseSafe -Dcpu=baseline -Dtarget=x86_64-linux-gnu +# ReleaseFast (not ReleaseSafe): Io.Uring fiber context-switch GPFs under ReleaseSafe +# due to a zig 0.16-dev optimizer+safety interaction bug. See repro_evented.zig. +RUN zig build -Doptimize=ReleaseFast -Dcpu=baseline -Dtarget=x86_64-linux-gnu FROM --platform=linux/amd64 debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* diff --git a/repro_evented.zig b/repro_evented.zig new file mode 100644 index 0000000..24e0e6c --- /dev/null +++ b/repro_evented.zig @@ -0,0 +1,70 @@ +///! minimal reproduction: does Io.Evented fiber context-switch work? +///! tests several variants to isolate the failure mode +const std = @import("std"); +const Io = std.Io; + +var evented: Io.Evented = undefined; + +var debug_threaded_io: Io.Threaded = undefined; +pub const std_options_debug_threaded_io: ?*Io.Threaded = &debug_threaded_io; + +fn fiberNoSleep(_: Io) void { + std.debug.print(" fiber-nosleep: entered and returning\n", .{}); +} + +fn fiberSleep(io: Io) void { + std.debug.print(" fiber-sleep: entered, sleeping 50ms\n", .{}); + io.sleep(Io.Duration.fromMilliseconds(50), .awake) catch { + std.debug.print(" fiber-sleep: cancelled\n", .{}); + return; + }; + std.debug.print(" fiber-sleep: woke up\n", .{}); +} + +fn fiberYieldOnly(io: Io) void { + std.debug.print(" fiber-yield: entered, yielding\n", .{}); + io.sleep(Io.Duration.fromMilliseconds(0), .awake) catch { + std.debug.print(" fiber-yield: cancelled\n", .{}); + return; + }; + std.debug.print(" fiber-yield: resumed\n", .{}); +} + +pub fn main() !void { + const allocator = std.heap.c_allocator; + + debug_threaded_io = Io.Threaded.init(allocator, .{}); + + try Io.Evented.init(&evented, allocator, .{}); + const io = evented.io(); + std.debug.print("evented init: ok\n", .{}); + + // test 1: fiber that returns immediately (no yield) + std.debug.print("test 1: fiber returns immediately\n", .{}); + { + var f = try io.concurrent(fiberNoSleep, .{io}); + io.sleep(Io.Duration.fromMilliseconds(10), .awake) catch {}; + f.cancel(io); + } + std.debug.print("test 1: PASSED\n\n", .{}); + + // test 2: fiber that yields once (0ms sleep) + std.debug.print("test 2: fiber yields once\n", .{}); + { + var f = try io.concurrent(fiberYieldOnly, .{io}); + io.sleep(Io.Duration.fromMilliseconds(50), .awake) catch {}; + f.cancel(io); + } + std.debug.print("test 2: PASSED\n\n", .{}); + + // test 3: fiber that sleeps 50ms + std.debug.print("test 3: fiber sleeps 50ms\n", .{}); + { + var f = try io.concurrent(fiberSleep, .{io}); + io.sleep(Io.Duration.fromMilliseconds(200), .awake) catch {}; + f.cancel(io); + } + std.debug.print("test 3: PASSED\n\n", .{}); + + std.debug.print("all tests passed\n", .{}); +} diff --git a/src/main.zig b/src/main.zig index 9a7286f..5e9cd65 100644 --- a/src/main.zig +++ b/src/main.zig @@ -50,6 +50,10 @@ pub const default_stack_size = 8 * 1024 * 1024; // Evented (fibers): network orchestration, subscriber connections, WS server, broadcasting. // Worker threads use a dedicated pool_io (Threaded) for their sync — they never touch Evented io. // The broadcast queue bridges workers → broadcaster fiber (atomics only, no Io dependency). +// +// NOTE: Io.Uring has a fiber context-switch GPF under ReleaseSafe (optimizer + safety checks). +// Debug and ReleaseFast both work fine. Build with ReleaseFast until the stdlib bug is fixed. +// See repro_evented.zig for the minimal reproduction case. const Backend = Io.Evented; var backend: Backend = undefined; -- 2.51.2