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;