atproto relay implementation in zig zlay.waow.tech
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix evented GPF: ReleaseSafe optimizer bug, build with ReleaseFast

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 <noreply@anthropic.com>

+77 -1
+3 -1
Dockerfile
··· 14 14 15 15 # then copy source and build 16 16 COPY src/ src/ 17 - RUN zig build -Doptimize=ReleaseSafe -Dcpu=baseline -Dtarget=x86_64-linux-gnu 17 + # ReleaseFast (not ReleaseSafe): Io.Uring fiber context-switch GPFs under ReleaseSafe 18 + # due to a zig 0.16-dev optimizer+safety interaction bug. See repro_evented.zig. 19 + RUN zig build -Doptimize=ReleaseFast -Dcpu=baseline -Dtarget=x86_64-linux-gnu 18 20 19 21 FROM --platform=linux/amd64 debian:bookworm-slim 20 22 RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
+70
repro_evented.zig
··· 1 + ///! minimal reproduction: does Io.Evented fiber context-switch work? 2 + ///! tests several variants to isolate the failure mode 3 + const std = @import("std"); 4 + const Io = std.Io; 5 + 6 + var evented: Io.Evented = undefined; 7 + 8 + var debug_threaded_io: Io.Threaded = undefined; 9 + pub const std_options_debug_threaded_io: ?*Io.Threaded = &debug_threaded_io; 10 + 11 + fn fiberNoSleep(_: Io) void { 12 + std.debug.print(" fiber-nosleep: entered and returning\n", .{}); 13 + } 14 + 15 + fn fiberSleep(io: Io) void { 16 + std.debug.print(" fiber-sleep: entered, sleeping 50ms\n", .{}); 17 + io.sleep(Io.Duration.fromMilliseconds(50), .awake) catch { 18 + std.debug.print(" fiber-sleep: cancelled\n", .{}); 19 + return; 20 + }; 21 + std.debug.print(" fiber-sleep: woke up\n", .{}); 22 + } 23 + 24 + fn fiberYieldOnly(io: Io) void { 25 + std.debug.print(" fiber-yield: entered, yielding\n", .{}); 26 + io.sleep(Io.Duration.fromMilliseconds(0), .awake) catch { 27 + std.debug.print(" fiber-yield: cancelled\n", .{}); 28 + return; 29 + }; 30 + std.debug.print(" fiber-yield: resumed\n", .{}); 31 + } 32 + 33 + pub fn main() !void { 34 + const allocator = std.heap.c_allocator; 35 + 36 + debug_threaded_io = Io.Threaded.init(allocator, .{}); 37 + 38 + try Io.Evented.init(&evented, allocator, .{}); 39 + const io = evented.io(); 40 + std.debug.print("evented init: ok\n", .{}); 41 + 42 + // test 1: fiber that returns immediately (no yield) 43 + std.debug.print("test 1: fiber returns immediately\n", .{}); 44 + { 45 + var f = try io.concurrent(fiberNoSleep, .{io}); 46 + io.sleep(Io.Duration.fromMilliseconds(10), .awake) catch {}; 47 + f.cancel(io); 48 + } 49 + std.debug.print("test 1: PASSED\n\n", .{}); 50 + 51 + // test 2: fiber that yields once (0ms sleep) 52 + std.debug.print("test 2: fiber yields once\n", .{}); 53 + { 54 + var f = try io.concurrent(fiberYieldOnly, .{io}); 55 + io.sleep(Io.Duration.fromMilliseconds(50), .awake) catch {}; 56 + f.cancel(io); 57 + } 58 + std.debug.print("test 2: PASSED\n\n", .{}); 59 + 60 + // test 3: fiber that sleeps 50ms 61 + std.debug.print("test 3: fiber sleeps 50ms\n", .{}); 62 + { 63 + var f = try io.concurrent(fiberSleep, .{io}); 64 + io.sleep(Io.Duration.fromMilliseconds(200), .awake) catch {}; 65 + f.cancel(io); 66 + } 67 + std.debug.print("test 3: PASSED\n\n", .{}); 68 + 69 + std.debug.print("all tests passed\n", .{}); 70 + }
+4
src/main.zig
··· 50 50 // Evented (fibers): network orchestration, subscriber connections, WS server, broadcasting. 51 51 // Worker threads use a dedicated pool_io (Threaded) for their sync — they never touch Evented io. 52 52 // The broadcast queue bridges workers → broadcaster fiber (atomics only, no Io dependency). 53 + // 54 + // NOTE: Io.Uring has a fiber context-switch GPF under ReleaseSafe (optimizer + safety checks). 55 + // Debug and ReleaseFast both work fine. Build with ReleaseFast until the stdlib bug is fixed. 56 + // See repro_evented.zig for the minimal reproduction case. 53 57 const Backend = Io.Evented; 54 58 55 59 var backend: Backend = undefined;