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

Configure Feed

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

use page_allocator for per-frame arenas to fix linear RSS growth

glibc malloc was not returning freed arena pages to the OS under
high-churn multi-thread load (~700 frames/sec across 2250+ subscriber
threads + 16 worker threads), causing linear RSS growth from 0 to
~3.5 GiB over 12h despite correct defer arena.deinit() everywhere.

page_allocator uses mmap/munmap directly — pages are guaranteed to
return to the OS on deinit(). ArenaAllocator batches small allocs
into chunks, so actual mmap calls per frame are ~1-3 (negligible).

long-lived state (caches, DB, ring buffer) stays on c_allocator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz f6f9d9b4 1ecf3655

+7 -5
+1 -1
src/frame_worker.zig
··· 33 33 pub fn processFrame(work: *FrameWork) void { 34 34 defer work.allocator.free(work.data); 35 35 36 - var arena = std.heap.ArenaAllocator.init(work.allocator); 36 + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 37 37 defer arena.deinit(); 38 38 const alloc = arena.allocator(); 39 39
+5 -3
src/main.zig
··· 119 119 } 120 120 121 121 pub fn main() !void { 122 - // use libc allocator — glibc malloc has per-thread arenas, madvise-based page 123 - // return, and proven fragmentation mitigation. GPA is a debug allocator that 124 - // tracks per-allocation metadata and never returns freed pages to the OS. 122 + // use libc allocator for long-lived state (caches, DB connections, host maps). 123 + // per-frame ArenaAllocators use page_allocator instead — glibc malloc was not 124 + // returning freed arena pages to the OS under high-churn multi-thread load, 125 + // causing linear RSS growth (~3.5 GiB over 12h). page_allocator uses mmap/munmap 126 + // directly, guaranteeing page return on arena.deinit(). 125 127 const allocator = std.heap.c_allocator; 126 128 127 129 // parse config from env
+1 -1
src/subscriber.zig
··· 257 257 const sub = self.subscriber; 258 258 259 259 // lightweight header decode for cursor tracking + routing 260 - var arena = std.heap.ArenaAllocator.init(sub.allocator); 260 + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 261 261 defer arena.deinit(); 262 262 const alloc = arena.allocator(); 263 263