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:
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());
+}