authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-14 18:31:48-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-14 18:31:48-08:00
logd9fc7fa04db797d7b27dab2d9d6f56f63848da76
tree43d7cb9e0e38840d767a85679b7305c306aafc09
parentc6eeae8a8c2314fd35b5baddb18f33b0122d319f

std.Io: remove select function

This function works with a slice of futures and returns the index of a completed one. This doesn't work very well in practice because it's either too high level or too low level. At the lower level we have Io.Batch for doing this kind of thing at the Operation API layer. At the higher level we have Io.Select which is a convenience wrapper around an Io.Group and an Io.Queue.

6 files changed, 2 insertions(+), 273 deletions(-)

lib/std/Io.zig-38
...@@ -144,10 +144,6 @@ pub const VTable = struct {...@@ -144,10 +144,6 @@ pub const VTable = struct {
144 swapCancelProtection: *const fn (?*anyopaque, new: CancelProtection) CancelProtection,144 swapCancelProtection: *const fn (?*anyopaque, new: CancelProtection) CancelProtection,
145 checkCancel: *const fn (?*anyopaque) Cancelable!void,145 checkCancel: *const fn (?*anyopaque) Cancelable!void,
146146
147 /// Blocks until one of the futures from the list has a result ready, such
148 /// that awaiting it will not block. Returns that index.
149 select: *const fn (?*anyopaque, futures: []const *AnyFuture) Cancelable!usize,
150
151 futexWait: *const fn (?*anyopaque, ptr: *const u32, expected: u32, Timeout) Cancelable!void,147 futexWait: *const fn (?*anyopaque, ptr: *const u32, expected: u32, Timeout) Cancelable!void,
152 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,148 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
153 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,149 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
...@@ -2120,40 +2116,6 @@ pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void {...@@ -2120,40 +2116,6 @@ pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void {
2120 } });2116 } });
2121}2117}
21222118
2123/// Given a struct with each field a `*Future`, returns a union with the same
2124/// fields, each field type the future's result.
2125pub fn SelectUnion(S: type) type {
2126 const struct_fields = @typeInfo(S).@"struct".fields;
2127 var names: [struct_fields.len][]const u8 = undefined;
2128 var types: [struct_fields.len]type = undefined;
2129 for (struct_fields, &names, &types) |struct_field, *union_field_name, *UnionFieldType| {
2130 const FieldFuture = @typeInfo(struct_field.type).pointer.child;
2131 union_field_name.* = struct_field.name;
2132 UnionFieldType.* = @FieldType(FieldFuture, "result");
2133 }
2134 return @Union(.auto, std.meta.FieldEnum(S), &names, &types, &@splat(.{}));
2135}
2136
2137/// `s` is a struct with every field a `*Future(T)`, where `T` can be any type,
2138/// and can be different for each field.
2139pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
2140 const U = SelectUnion(@TypeOf(s));
2141 const S = @TypeOf(s);
2142 const fields = @typeInfo(S).@"struct".fields;
2143 var futures: [fields.len]*AnyFuture = undefined;
2144 inline for (fields, &futures) |field, *any_future| {
2145 const future = @field(s, field.name);
2146 any_future.* = future.any_future orelse return @unionInit(U, field.name, future.result);
2147 }
2148 switch (try io.vtable.select(io.userdata, &futures)) {
2149 inline 0...(fields.len - 1) => |selected_index| {
2150 const field_name = fields[selected_index].name;
2151 return @unionInit(U, field_name, @field(s, field_name).await(io));
2152 },
2153 else => unreachable,
2154 }
2155}
2156
2157pub const LockedStderr = struct {2119pub const LockedStderr = struct {
2158 file_writer: *File.Writer,2120 file_writer: *File.Writer,
2159 terminal_mode: Terminal.Mode,2121 terminal_mode: Terminal.Mode,
lib/std/Io/Dispatch.zig-50
...@@ -123,7 +123,6 @@ const Fiber = struct {...@@ -123,7 +123,6 @@ const Fiber = struct {
123 const Awaiting = enum(@Int(.unsigned, @bitSizeOf(usize) - shift)) {123 const Awaiting = enum(@Int(.unsigned, @bitSizeOf(usize) - shift)) {
124 nothing = 0,124 nothing = 0,
125 group = 1,125 group = 1,
126 select = 2,
127 _,126 _,
128127
129 const shift = 1;128 const shift = 1;
...@@ -277,9 +276,6 @@ const Fiber = struct {...@@ -277,9 +276,6 @@ const Fiber = struct {
277 ev.queue.async(fiber, &Fiber.@"resume");276 ev.queue.async(fiber, &Fiber.@"resume");
278 }277 }
279 },278 },
280 .select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
281 ev.queue.async(fiber, &Fiber.@"resume");
282 },
283 _ => |awaiting| awaiting.toCancelable().async(),279 _ => |awaiting| awaiting.toCancelable().async(),
284 }280 }
285 }281 }
...@@ -370,8 +366,6 @@ pub fn io(ev: *Evented) Io {...@@ -370,8 +366,6 @@ pub fn io(ev: *Evented) Io {
370 .swapCancelProtection = swapCancelProtection,366 .swapCancelProtection = swapCancelProtection,
371 .checkCancel = checkCancel,367 .checkCancel = checkCancel,
372368
373 .select = select,
374
375 .futexWait = futexWait,369 .futexWait = futexWait,
376 .futexWaitUncancelable = futexWaitUncancelable,370 .futexWaitUncancelable = futexWaitUncancelable,
377 .futexWake = futexWake,371 .futexWake = futexWake,
...@@ -1689,50 +1683,6 @@ fn futexForAddress(ev: *Evented, address: usize) *Futex {...@@ -1689,50 +1683,6 @@ fn futexForAddress(ev: *Evented, address: usize) *Futex {
1689 return &ev.futexes[hashed >> @clz(ev.futexes.len - 1)];1683 return &ev.futexes[hashed >> @clz(ev.futexes.len - 1)];
1690}1684}
16911685
1692fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
1693 const ev: *Evented = @ptrCast(@alignCast(userdata));
1694 const fiber = Thread.current().currentFiber();
1695 var await_count: u31, var result = for (futures, 0..) |future, future_index| {
1696 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1697 if (@atomicRmw(
1698 ?*Fiber,
1699 &future_fiber.link.awaiter,
1700 .Xchg,
1701 fiber,
1702 .acq_rel,
1703 )) |awaiter| {
1704 assert(awaiter == Fiber.finished);
1705 break .{ @intCast(future_index), future_index };
1706 }
1707 } else result: {
1708 const await_count: u31 = @intCast(futures.len);
1709 ev.yield(.{ .await = 1 });
1710 break :result .{ await_count - 1, futures.len };
1711 };
1712 for (futures[0..result], 0..) |future, future_index| {
1713 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1714 const awaiter = @atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, null, .monotonic);
1715 if (awaiter == Fiber.finished) {
1716 @atomicStore(?*Fiber, &future_fiber.link.awaiter, Fiber.finished, .monotonic);
1717 result = @min(future_index, result);
1718 } else {
1719 assert(awaiter == fiber);
1720 await_count -= 1;
1721 }
1722 }
1723 // Equivalent to `ev.yield(null, .{ .await = await_count });`,
1724 // but avoiding a context switch in the common case.
1725 switch (std.math.order(
1726 @atomicRmw(i32, &fiber.await_count, .Sub, await_count, .monotonic),
1727 await_count,
1728 )) {
1729 .lt => ev.yield(.{ .await = 0 }),
1730 .eq => {},
1731 .gt => unreachable,
1732 }
1733 return result;
1734}
1735
1736fn futexWait(1686fn futexWait(
1737 userdata: ?*anyopaque,1687 userdata: ?*anyopaque,
1738 ptr: *const u32,1688 ptr: *const u32,
lib/std/Io/Kqueue.zig-22
...@@ -491,7 +491,6 @@ const SwitchMessage = struct {...@@ -491,7 +491,6 @@ const SwitchMessage = struct {
491 reschedule,491 reschedule,
492 recycle: *Fiber,492 recycle: *Fiber,
493 register_awaiter: *?*Fiber,493 register_awaiter: *?*Fiber,
494 register_select: []const *Io.AnyFuture,
495 exit,494 exit,
496 };495 };
497496
...@@ -514,19 +513,6 @@ const SwitchMessage = struct {...@@ -514,19 +513,6 @@ const SwitchMessage = struct {
514 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)513 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)
515 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });514 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
516 },515 },
517 .register_select => |futures| {
518 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
519 assert(prev_fiber.queue_next == null);
520 for (futures) |any_future| {
521 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
522 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
523 const closure: *AsyncClosure = .fromFiber(future_fiber);
524 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
525 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
526 }
527 }
528 }
529 },
530 .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {516 .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {
531 const changes = [_]posix.Kevent{517 const changes = [_]posix.Kevent{
532 .{518 .{
...@@ -628,7 +614,6 @@ pub fn io(k: *Kqueue) Io {...@@ -628,7 +614,6 @@ pub fn io(k: *Kqueue) Io {
628 .concurrent = concurrent,614 .concurrent = concurrent,
629 .await = await,615 .await = await,
630 .cancel = cancel,616 .cancel = cancel,
631 .select = select,
632617
633 .groupAsync = groupAsync,618 .groupAsync = groupAsync,
634 .groupConcurrent = groupConcurrent,619 .groupConcurrent = groupConcurrent,
...@@ -824,13 +809,6 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void...@@ -824,13 +809,6 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void
824 @panic("TODO");809 @panic("TODO");
825}810}
826811
827fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
828 const k: *Kqueue = @ptrCast(@alignCast(userdata));
829 _ = k;
830 _ = futures;
831 @panic("TODO");
832}
833
834fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {812fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
835 const k: *Kqueue = @ptrCast(@alignCast(userdata));813 const k: *Kqueue = @ptrCast(@alignCast(userdata));
836 _ = k;814 _ = k;
lib/std/Io/Threaded.zig-70
...@@ -1772,7 +1772,6 @@ pub fn io(t: *Threaded) Io {...@@ -1772,7 +1772,6 @@ pub fn io(t: *Threaded) Io {
1772 .concurrent = concurrent,1772 .concurrent = concurrent,
1773 .await = await,1773 .await = await,
1774 .cancel = cancel,1774 .cancel = cancel,
1775 .select = select,
17761775
1777 .groupAsync = groupAsync,1776 .groupAsync = groupAsync,
1778 .groupConcurrent = groupConcurrent,1777 .groupConcurrent = groupConcurrent,
...@@ -1938,7 +1937,6 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1938,7 +1937,6 @@ pub fn ioBasic(t: *Threaded) Io {
1938 .concurrent = concurrent,1937 .concurrent = concurrent,
1939 .await = await,1938 .await = await,
1940 .cancel = cancel,1939 .cancel = cancel,
1941 .select = select,
19421940
1943 .groupAsync = groupAsync,1941 .groupAsync = groupAsync,
1944 .groupConcurrent = groupConcurrent,1942 .groupConcurrent = groupConcurrent,
...@@ -11727,74 +11725,6 @@ fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {...@@ -11727,74 +11725,6 @@ fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
11727 }11725 }
11728}11726}
1172911727
11730fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
11731 const t: *Threaded = @ptrCast(@alignCast(userdata));
11732 _ = t;
11733
11734 var num_completed: std.atomic.Value(u32) = .init(0);
11735
11736 for (futures, 0..) |any_future, i| {
11737 const future: *Future = @ptrCast(@alignCast(any_future));
11738 future.awaiter = &num_completed;
11739 const old_status = future.status.fetchOr(
11740 .{ .tag = .pending_awaited, .thread = .null },
11741 .release, // release `future.awaiter`
11742 );
11743 switch (old_status.tag) {
11744 .pending => {},
11745 .pending_awaited => unreachable, // `await` raced with `select`
11746 .pending_canceled => unreachable, // `cancel` raced with `select`
11747 .done => {
11748 future.status.store(old_status, .monotonic);
11749 _ = finishSelect(&num_completed, futures[0..i]);
11750 return i;
11751 },
11752 }
11753 }
11754
11755 errdefer _ = finishSelect(&num_completed, futures);
11756
11757 while (true) {
11758 const n = num_completed.load(.acquire);
11759 if (n > 0) break;
11760 assert(n < futures.len);
11761 try Thread.futexWait(&num_completed.raw, n, null);
11762 }
11763 return finishSelect(&num_completed, futures).?;
11764}
11765fn finishSelect(
11766 num_completed: *std.atomic.Value(u32),
11767 futures: []const *Io.AnyFuture,
11768) ?usize {
11769 var completed_index: ?usize = null;
11770 var expect_completed: u32 = 0;
11771 for (futures, 0..) |any_future, i| {
11772 const future: *Future = @ptrCast(@alignCast(any_future));
11773 // This operation will convert `.pending_awaited` to `.pending`, or leave `.done` untouched.
11774 switch (future.status.fetchAnd(
11775 .{ .tag = @enumFromInt(0b10), .thread = .all_ones },
11776 .monotonic,
11777 ).tag) {
11778 .pending_awaited => {},
11779 .pending => unreachable,
11780 .pending_canceled => unreachable,
11781 .done => {
11782 expect_completed += 1;
11783 completed_index = i;
11784 },
11785 }
11786 }
11787 // If any future has just finished, wait for it to signal `num_completed` to avoid dangling
11788 // references to stack memory.
11789 while (true) {
11790 const n = num_completed.load(.acquire);
11791 if (n == expect_completed) break;
11792 assert(n < expect_completed);
11793 Thread.futexWaitUncancelable(&num_completed.raw, n, null);
11794 }
11795 return completed_index;
11796}
11797
11798fn netListenIpPosix(11728fn netListenIpPosix(
11799 userdata: ?*anyopaque,11729 userdata: ?*anyopaque,
11800 address: IpAddress,11730 address: IpAddress,
lib/std/Io/Uring.zig+2-59
...@@ -175,7 +175,6 @@ const Fiber = struct {...@@ -175,7 +175,6 @@ const Fiber = struct {
175 const Awaiting = enum(u31) {175 const Awaiting = enum(u31) {
176 nothing = std.math.maxInt(u31),176 nothing = std.math.maxInt(u31),
177 group = std.math.maxInt(u31) - 1,177 group = std.math.maxInt(u31) - 1,
178 select = std.math.maxInt(u31) - 2,
179 /// An io_uring fd.178 /// An io_uring fd.
180 _,179 _,
181180
...@@ -186,14 +185,14 @@ const Fiber = struct {...@@ -186,14 +185,14 @@ const Fiber = struct {
186 fn fromIoUringFd(fd: fd_t) Awaiting {185 fn fromIoUringFd(fd: fd_t) Awaiting {
187 const awaiting: Awaiting = @enumFromInt(fd);186 const awaiting: Awaiting = @enumFromInt(fd);
188 switch (awaiting) {187 switch (awaiting) {
189 .nothing, .group, .select => unreachable,188 .nothing, .group => unreachable,
190 _ => return awaiting,189 _ => return awaiting,
191 }190 }
192 }191 }
193192
194 fn toIoUringFd(awaiting: Awaiting) fd_t {193 fn toIoUringFd(awaiting: Awaiting) fd_t {
195 switch (awaiting) {194 switch (awaiting) {
196 .nothing, .group, .select => unreachable,195 .nothing, .group => unreachable,
197 _ => return @intFromEnum(awaiting),196 _ => return @intFromEnum(awaiting),
198 }197 }
199 }198 }
...@@ -376,9 +375,6 @@ const Fiber = struct {...@@ -376,9 +375,6 @@ const Fiber = struct {
376 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });375 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
377 }376 }
378 },377 },
379 .select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
380 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
381 },
382 _ => |awaiting| {378 _ => |awaiting| {
383 const awaiting_io_uring_fd = awaiting.toIoUringFd();379 const awaiting_io_uring_fd = awaiting.toIoUringFd();
384 const thread: *Thread = .current();380 const thread: *Thread = .current();
...@@ -684,8 +680,6 @@ pub fn io(ev: *Evented) Io {...@@ -684,8 +680,6 @@ pub fn io(ev: *Evented) Io {
684 .swapCancelProtection = swapCancelProtection,680 .swapCancelProtection = swapCancelProtection,
685 .checkCancel = checkCancel,681 .checkCancel = checkCancel,
686682
687 .select = select,
688
689 .futexWait = futexWait,683 .futexWait = futexWait,
690 .futexWaitUncancelable = futexWaitUncancelable,684 .futexWaitUncancelable = futexWaitUncancelable,
691 .futexWake = futexWake,685 .futexWake = futexWake,
...@@ -1928,57 +1922,6 @@ fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {...@@ -1928,57 +1922,6 @@ fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {
1928 }1922 }
1929}1923}
19301924
1931fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
1932 const ev: *Evented = @ptrCast(@alignCast(userdata));
1933 var cancel_region: CancelRegion = .init();
1934 defer cancel_region.deinit();
1935 var await_count: u31, var result = for (futures, 0..) |future, future_index| {
1936 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1937 if (@atomicRmw(
1938 ?*Fiber,
1939 &future_fiber.link.awaiter,
1940 .Xchg,
1941 cancel_region.fiber,
1942 .acq_rel,
1943 )) |awaiter| {
1944 assert(awaiter == Fiber.finished);
1945 break .{ @intCast(future_index), future_index };
1946 }
1947 } else result: {
1948 const await_count: u31 = @intCast(futures.len);
1949 cancel_region.await(.select) catch |err| switch (err) {
1950 error.Canceled => |e| break :result .{ await_count + 1, e },
1951 };
1952 ev.yield(null, .{ .await = 1 });
1953 cancel_region.await(.nothing) catch |err| switch (err) {
1954 error.Canceled => |e| break :result .{ await_count, e },
1955 };
1956 break :result .{ await_count - 1, futures.len };
1957 };
1958 for (futures[0 .. result catch futures.len], 0..) |future, future_index| {
1959 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1960 const awaiter = @atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, null, .monotonic);
1961 if (awaiter == Fiber.finished) {
1962 @atomicStore(?*Fiber, &future_fiber.link.awaiter, Fiber.finished, .monotonic);
1963 result = if (result) |finished_index| @min(future_index, finished_index) else |e| e;
1964 } else {
1965 assert(awaiter == cancel_region.fiber);
1966 await_count -= 1;
1967 }
1968 }
1969 // Equivalent to `ev.yield(null, .{ .await = await_count });`,
1970 // but avoiding a context switch in the common case.
1971 switch (std.math.order(
1972 @atomicRmw(i32, &cancel_region.fiber.await_count, .Sub, await_count, .monotonic),
1973 await_count,
1974 )) {
1975 .lt => ev.yield(null, .{ .await = 0 }),
1976 .eq => {},
1977 .gt => unreachable,
1978 }
1979 return result;
1980}
1981
1982fn futexWait(1925fn futexWait(
1983 userdata: ?*anyopaque,1926 userdata: ?*anyopaque,
1984 ptr: *const u32,1927 ptr: *const u32,
lib/std/Io/test.zig-34
...@@ -282,40 +282,6 @@ test "Group.concurrent" {...@@ -282,40 +282,6 @@ test "Group.concurrent" {
282 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);282 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
283}283}
284284
285test "select" {
286 const io = testing.io;
287
288 var queue: Io.Queue(u8) = .init(&.{});
289
290 var get_a = io.concurrent(Io.Queue(u8).getOne, .{ &queue, io }) catch |err| switch (err) {
291 error.ConcurrencyUnavailable => {
292 try testing.expect(builtin.single_threaded);
293 return;
294 },
295 };
296 defer _ = get_a.cancel(io) catch {};
297
298 var get_b = try io.concurrent(Io.Queue(u8).getOne, .{ &queue, io });
299 defer _ = get_b.cancel(io) catch {};
300
301 var timeout = io.async(Io.sleep, .{ io, .fromMilliseconds(1), .awake });
302 defer timeout.cancel(io) catch {};
303
304 switch (try io.select(.{
305 .get_a = &get_a,
306 .get_b = &get_b,
307 .timeout = &timeout,
308 })) {
309 .get_a => return error.TestFailure,
310 .get_b => return error.TestFailure,
311 .timeout => {
312 queue.close(io);
313 try testing.expectError(error.Closed, get_a.await(io));
314 try testing.expectError(error.Closed, get_b.await(io));
315 },
316 }
317}
318
319fn testQueue(comptime len: usize) !void {285fn testQueue(comptime len: usize) !void {
320 const io = testing.io;286 const io = testing.io;
321 var buf: [len]usize = undefined;287 var buf: [len]usize = undefined;