zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 0a412853aae9815eb663a88a8a2d37b91c614317 (tree)
parent ac24e6caf5a79573f16d2ccc273d907ad2199032
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Mon,  2 Mar 2026 08:30:42 -0800

std.Io: fix Select cancel deadlock with no tasks

Diffstat:
Mlib/std/Io.zig | 9+++------
Mlib/std/Io/test.zig | 11+++++++++++
2 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/lib/std/Io.zig b/lib/std/Io.zig @@ -1446,11 +1446,8 @@ pub fn Select(comptime U: type) type { /// Threadsafe. pub fn cancel(s: *S) ?U { const io = s.io; - if (s.group.token.load(.acquire)) |token| { - io.vtable.groupCancel(io.userdata, &s.group, token); - assert(s.group.token.raw == null); - s.queue.close(io); - } + s.group.cancel(io); + s.queue.close(io); return s.queue.getOneUncancelable(io) catch |err| switch (err) { error.Closed => return null, }; @@ -1855,7 +1852,7 @@ pub const TypeErasedQueue = struct { /// there is space in the buffer. However, existing elements of the /// queue are retrieved before `error.Closed` is returned. /// - /// Threadsafe. + /// Idempotent. Threadsafe. pub fn close(q: *TypeErasedQueue, io: Io) void { q.mutex.lockUncancelable(io); defer q.mutex.unlock(io); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig @@ -937,3 +937,14 @@ test "Select with empty buffer, no deadlock" { }; assert((try select.await()) == .sleeper); } + +test "Select.cancel with no tasks, no deadlock" { + const io = testing.io; + + const U = union(enum) { + nothing: void, + also_nothing: void, + }; + var select: Io.Select(U) = .init(io, &.{}); + try expectEqual(null, select.cancel()); +}