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 {
144144 swapCancelProtection: *const fn (?*anyopaque, new: CancelProtection) CancelProtection,
145145 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
151147 futexWait: *const fn (?*anyopaque, ptr: *const u32, expected: u32, Timeout) Cancelable!void,
152148 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
153149 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 {
21202116 } });
21212117}
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
21572119pub const LockedStderr = struct {
21582120 file_writer: *File.Writer,
21592121 terminal_mode: Terminal.Mode,
lib/std/Io/Dispatch.zig-50
......@@ -123,7 +123,6 @@ const Fiber = struct {
123123 const Awaiting = enum(@Int(.unsigned, @bitSizeOf(usize) - shift)) {
124124 nothing = 0,
125125 group = 1,
126 select = 2,
127126 _,
128127
129128 const shift = 1;
......@@ -277,9 +276,6 @@ const Fiber = struct {
277276 ev.queue.async(fiber, &Fiber.@"resume");
278277 }
279278 },
280 .select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
281 ev.queue.async(fiber, &Fiber.@"resume");
282 },
283279 _ => |awaiting| awaiting.toCancelable().async(),
284280 }
285281 }
......@@ -370,8 +366,6 @@ pub fn io(ev: *Evented) Io {
370366 .swapCancelProtection = swapCancelProtection,
371367 .checkCancel = checkCancel,
372368
373 .select = select,
374
375369 .futexWait = futexWait,
376370 .futexWaitUncancelable = futexWaitUncancelable,
377371 .futexWake = futexWake,
......@@ -1689,50 +1683,6 @@ fn futexForAddress(ev: *Evented, address: usize) *Futex {
16891683 return &ev.futexes[hashed >> @clz(ev.futexes.len - 1)];
16901684}
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
17361686fn futexWait(
17371687 userdata: ?*anyopaque,
17381688 ptr: *const u32,
lib/std/Io/Kqueue.zig-22
......@@ -491,7 +491,6 @@ const SwitchMessage = struct {
491491 reschedule,
492492 recycle: *Fiber,
493493 register_awaiter: *?*Fiber,
494 register_select: []const *Io.AnyFuture,
495494 exit,
496495 };
497496
......@@ -514,19 +513,6 @@ const SwitchMessage = struct {
514513 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)
515514 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
516515 },
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 },
530516 .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {
531517 const changes = [_]posix.Kevent{
532518 .{
......@@ -628,7 +614,6 @@ pub fn io(k: *Kqueue) Io {
628614 .concurrent = concurrent,
629615 .await = await,
630616 .cancel = cancel,
631 .select = select,
632617
633618 .groupAsync = groupAsync,
634619 .groupConcurrent = groupConcurrent,
......@@ -824,13 +809,6 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void
824809 @panic("TODO");
825810}
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
834812fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
835813 const k: *Kqueue = @ptrCast(@alignCast(userdata));
836814 _ = k;
lib/std/Io/Threaded.zig-70
......@@ -1772,7 +1772,6 @@ pub fn io(t: *Threaded) Io {
17721772 .concurrent = concurrent,
17731773 .await = await,
17741774 .cancel = cancel,
1775 .select = select,
17761775
17771776 .groupAsync = groupAsync,
17781777 .groupConcurrent = groupConcurrent,
......@@ -1938,7 +1937,6 @@ pub fn ioBasic(t: *Threaded) Io {
19381937 .concurrent = concurrent,
19391938 .await = await,
19401939 .cancel = cancel,
1941 .select = select,
19421940
19431941 .groupAsync = groupAsync,
19441942 .groupConcurrent = groupConcurrent,
......@@ -11727,74 +11725,6 @@ fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
1172711725 }
1172811726}
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
1179811728fn netListenIpPosix(
1179911729 userdata: ?*anyopaque,
1180011730 address: IpAddress,
lib/std/Io/Uring.zig+2-59
......@@ -175,7 +175,6 @@ const Fiber = struct {
175175 const Awaiting = enum(u31) {
176176 nothing = std.math.maxInt(u31),
177177 group = std.math.maxInt(u31) - 1,
178 select = std.math.maxInt(u31) - 2,
179178 /// An io_uring fd.
180179 _,
181180
......@@ -186,14 +185,14 @@ const Fiber = struct {
186185 fn fromIoUringFd(fd: fd_t) Awaiting {
187186 const awaiting: Awaiting = @enumFromInt(fd);
188187 switch (awaiting) {
189 .nothing, .group, .select => unreachable,
188 .nothing, .group => unreachable,
190189 _ => return awaiting,
191190 }
192191 }
193192
194193 fn toIoUringFd(awaiting: Awaiting) fd_t {
195194 switch (awaiting) {
196 .nothing, .group, .select => unreachable,
195 .nothing, .group => unreachable,
197196 _ => return @intFromEnum(awaiting),
198197 }
199198 }
......@@ -376,9 +375,6 @@ const Fiber = struct {
376375 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
377376 }
378377 },
379 .select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
380 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
381 },
382378 _ => |awaiting| {
383379 const awaiting_io_uring_fd = awaiting.toIoUringFd();
384380 const thread: *Thread = .current();
......@@ -684,8 +680,6 @@ pub fn io(ev: *Evented) Io {
684680 .swapCancelProtection = swapCancelProtection,
685681 .checkCancel = checkCancel,
686682
687 .select = select,
688
689683 .futexWait = futexWait,
690684 .futexWaitUncancelable = futexWaitUncancelable,
691685 .futexWake = futexWake,
......@@ -1928,57 +1922,6 @@ fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {
19281922 }
19291923}
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
19821925fn futexWait(
19831926 userdata: ?*anyopaque,
19841927 ptr: *const u32,
lib/std/Io/test.zig-34
......@@ -282,40 +282,6 @@ test "Group.concurrent" {
282282 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
283283}
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
319285fn testQueue(comptime len: usize) !void {
320286 const io = testing.io;
321287 var buf: [len]usize = undefined;