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 gcLoop: silently exited after one tick

gcLoop was using io.sleep on pool_io (Threaded) from a plain std.Thread.
the first tick happened to succeed, the second hit an error path, and
catch return swallowed it — silently killing the loop. one malloc_trim
fired at the 10-min mark and then nothing for 13.5+ hours.

fix: switch to std.c.nanosleep directly. plain threads can't safely
call into Io scheduler primitives, even on the matching backend, because
they aren't registered with that backend's scheduler.

drop the io parameter from gcLoop since dp.gc() uses its own bound io
internally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+15 -9
+15 -9
src/main.zig
··· 351 351 // start GC loop on a plain thread — dp.gc() uses pool_io (Threaded) mutex 352 352 // and pg.Pool. MUST NOT run as Evented fiber: Threaded futex on Evented 353 353 // fiber dereferences NULL Thread.current() threadlocal → heap corruption. 354 - const gc_thread = std.Thread.spawn(.{}, gcLoop, .{ &dp, pool_io }) catch |err| { 354 + // sleeps via std.Thread.sleep (NOT io.sleep) — io.sleep on pool_io from 355 + // a non-Io thread fails on the second tick and silently kills the loop. 356 + const gc_thread = std.Thread.spawn(.{}, gcLoop, .{&dp}) catch |err| { 355 357 log.err("failed to start GC thread: {s}", .{@errorName(err)}); 356 358 return err; 357 359 }; ··· 462 464 server.runIo(listener, bc); 463 465 } 464 466 465 - fn gcLoop(dp: *event_log_mod.DiskPersist, io: Io) void { 466 - const gc_interval: u64 = 10 * 60; // 10 minutes in seconds 467 + fn gcLoop(dp: *event_log_mod.DiskPersist) void { 468 + const gc_interval_s: u64 = 10 * 60; // 10 minutes 467 469 while (!shutdown_flag.load(.acquire)) { 468 - // sleep in small increments to check shutdown 469 - var remaining: u64 = gc_interval; 470 - while (remaining > 0 and !shutdown_flag.load(.acquire)) { 471 - const chunk = @min(remaining, 1); 472 - io.sleep(Io.Duration.fromSeconds(@intCast(chunk)), .awake) catch return; 473 - remaining -= chunk; 470 + // sleep in 1s ticks so shutdown is checked frequently. uses 471 + // std.c.nanosleep directly — this is a plain OS thread, so calling 472 + // io.sleep on pool_io would fail on the second tick and silently 473 + // exit the loop via `catch return`. zig 0.16 has no std.Thread.sleep 474 + // and std.posix.nanosleep was removed during the Io migration. 475 + var elapsed: u64 = 0; 476 + while (elapsed < gc_interval_s and !shutdown_flag.load(.acquire)) { 477 + const ts: std.c.timespec = .{ .sec = 1, .nsec = 0 }; 478 + _ = std.c.nanosleep(&ts, null); 479 + elapsed += 1; 474 480 } 475 481 if (shutdown_flag.load(.acquire)) return; 476 482