diff --git a/src/main.zig b/src/main.zig index 960a725..ffb9696 100644 --- a/src/main.zig +++ b/src/main.zig @@ -351,7 +351,9 @@ pub fn main() !void { // start GC loop on a plain thread — dp.gc() uses pool_io (Threaded) mutex // and pg.Pool. MUST NOT run as Evented fiber: Threaded futex on Evented // fiber dereferences NULL Thread.current() threadlocal → heap corruption. - const gc_thread = std.Thread.spawn(.{}, gcLoop, .{ &dp, pool_io }) catch |err| { + // sleeps via std.Thread.sleep (NOT io.sleep) — io.sleep on pool_io from + // a non-Io thread fails on the second tick and silently kills the loop. + const gc_thread = std.Thread.spawn(.{}, gcLoop, .{&dp}) catch |err| { log.err("failed to start GC thread: {s}", .{@errorName(err)}); return err; }; @@ -462,15 +464,19 @@ fn runWsServer(server: *websocket.Server(broadcaster.Handler), listener: *Io.net server.runIo(listener, bc); } -fn gcLoop(dp: *event_log_mod.DiskPersist, io: Io) void { - const gc_interval: u64 = 10 * 60; // 10 minutes in seconds +fn gcLoop(dp: *event_log_mod.DiskPersist) void { + const gc_interval_s: u64 = 10 * 60; // 10 minutes while (!shutdown_flag.load(.acquire)) { - // sleep in small increments to check shutdown - var remaining: u64 = gc_interval; - while (remaining > 0 and !shutdown_flag.load(.acquire)) { - const chunk = @min(remaining, 1); - io.sleep(Io.Duration.fromSeconds(@intCast(chunk)), .awake) catch return; - remaining -= chunk; + // sleep in 1s ticks so shutdown is checked frequently. uses + // std.c.nanosleep directly — this is a plain OS thread, so calling + // io.sleep on pool_io would fail on the second tick and silently + // exit the loop via `catch return`. zig 0.16 has no std.Thread.sleep + // and std.posix.nanosleep was removed during the Io migration. + var elapsed: u64 = 0; + while (elapsed < gc_interval_s and !shutdown_flag.load(.acquire)) { + const ts: std.c.timespec = .{ .sec = 1, .nsec = 0 }; + _ = std.c.nanosleep(&ts, null); + elapsed += 1; } if (shutdown_flag.load(.acquire)) return;