From ce96dcceab7731f63534425f1d25bfe9be014847 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Mon, 3 Aug 2026 14:35:39 -0400 Subject: [PATCH] fix(Io.Dispatch): node = .{}, mutex deinit, wrong canceled In most of the file `node: std.DoublyLinkedList.Node` is default-initialized as `.{}` rather than `undefined`. I don't believe this resolves any crashes for me, but it seems incorrect. If `backing_allocator_needs_mutex` is `true` and the backing allocator has been used nontrivially; for example in creating a `Group`, the current `Dispatch.deinit()` code segfaults. Since the error also goes away when `backing_allocator_needs_mutex` is false, I believe remembering to call `deinit()` on the mutex is the fix. Canceling a sleep call currently crashes because of a soundness issue: The `@fieldParentPtr` type punning in `SleepWaiter.canceled` cannot be replaced by `Futex.Waiter.canceled`. For me, the failure winds up being in `waiter.remove()` meeting a node whose `prev` field is mostly `0xaa`, which made this one a real pain to fix. --- lib/std/Io/Dispatch.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Dispatch.zig b/lib/std/Io/Dispatch.zig index 4f58bc8bdf7645ae97bd1131a13974552d07e7a8..1cbbcbaef64a35185ab51a6cc17abc56b4d246af 100644 --- a/lib/std/Io/Dispatch.zig +++ b/lib/std/Io/Dispatch.zig @@ -580,6 +580,7 @@ pub fn deinit(ev: *Evented) void { ev.stderr_mutex.deinit(); for (&ev.futexes) |*futex| futex.deinit(); ev.exit_semaphore.as_object().release(); + ev.backing_allocator_mutex.deinit(); ev.backing_allocator.free(ev.main_loop_stack[0..main_loop_stack_size]); ev.queue.as_object().release(); } @@ -825,7 +826,7 @@ const Mutex = struct { sleeper: Sleeper = undefined, cancelable: Cancelable, mutex: *Mutex, - node: std.DoublyLinkedList.Node = undefined, + node: std.DoublyLinkedList.Node = .{}, fn add(context: ?*anyopaque) callconv(.c) void { const waiter: *Waiter = @ptrCast(@alignCast(context)); @@ -4714,7 +4715,7 @@ fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void { return ev.yield(.{ .after = ev.timeFromTimeout(timeout) }); }; var waiter: SleepWaiter = .{ - .cancelable = .{ .queue = queue, .cancel = &Futex.Waiter.canceled }, + .cancelable = .{ .queue = queue, .cancel = &SleepWaiter.canceled }, .timer = timer, }; timer.as_object().set_context(&waiter); -- 2.54.0