From 80039680c2db3020ebedea121c1bf98806eb21ac Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Mon, 03 Aug 2026 15:46:41 +0000 Subject: [PATCH] frame pool: sync through the main io, not pool_io a fiber that parks through a Threaded io blocks its loop thread: a contended submit lock is a kernel futex wait, and full-queue backpressure is a 10ms thread sleep, taken on the zio loop thread. under load every subscriber fiber serializes on these and the broadcaster fiber starves — the queue fills and the relay stops. the main io's futex is safe from both domains: fibers park in the sched table, the pool's OS worker threads fall back to the kernel futex, and wakes reach both. under Io.Threaded this is a no-op. also bumps zio to 2a84a09 (DNS fiber wake + broadcast sentinel). Co-Authored-By: Claude Fable 5 --- build.zig.zon | 4 ++-- src/internal/slurper.zig | 12 +++++++++--- 2 file(s) changed, 11 insertion(s)(+), 5 deletion(s)(-) diff --git a/build.zig.zon b/build.zig.zon --- a/build.zig.zon +++ b/build.zig.zon @@ -13,8 +13,8 @@ .hash = "websocket-0.1.10-ZPISdUowBQAXIl_X9VpZozIxBLVf_o5wYjOMMdobrXF5", }, .zio = .{ - .url = "https://tangled.org/zzstoatzz.io/zio/archive/24341fe.tar.gz", - .hash = "zio-0.0.0-psl_OazUAQCps9d9iqRQi8btzvhPtnD_4qalnSEtPig4", + .url = "https://tangled.org/zzstoatzz.io/zio/archive/2a84a09.tar.gz", + .hash = "zio-0.0.0-psl_OQzcAQBqYcT3AES5hghOUX4vcNvsh8-_zCbEkl5S", }, .pg = .{ .url = "https://tangled.org/zzstoatzz.io/pg.zig/archive/c5a5607.tar.gz", diff --git a/src/internal/slurper.zig b/src/internal/slurper.zig --- a/src/internal/slurper.zig +++ b/src/internal/slurper.zig @@ -113,13 +113,19 @@ self.ca_bundle = bundle; log.info("loaded shared CA bundle", .{}); - // create frame processing pool — worker threads use pool_io (Threaded), - // safe from plain OS threads even when the app uses Evented io + // create frame processing pool. the pool's mutex/cond use the MAIN io, + // not pool_io: submitters can be fibers, and a fiber that parks through + // a Threaded io blocks its loop thread (contended lock = kernel futex + // wait, full-queue backpressure = thread sleep), starving every other + // fiber including the broadcaster. the main io's futex is safe from + // both domains — fibers park in the sched table, the pool's own OS + // worker threads fall back to the kernel futex, wakes reach both. + // under Io.Threaded main io and pool_io behave identically here. self.frame_pool = try frame_worker_mod.FramePool.init(self.allocator, .{ .num_workers = self.options.frame_workers, .queue_capacity = self.options.frame_queue_capacity, .stack_size = @import("../main.zig").default_stack_size, - }, self.pool_io); + }, self.io); log.info("frame pool started: {d} workers, queue capacity {d}", .{ self.options.frame_workers, self.options.frame_queue_capacity }); // spawn worker startup in background so HTTP server + probes come up immediately. -- tangled.sh