From 46fcf22630849f68f92aec64d38f44b0c4b3a943 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Wed, 14 Jan 2026 18:14:43 -0600 Subject: [PATCH 1/3] Io.Select: docs nits --- lib/std/Io.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index b4d6efc715..18aa1e127a 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1250,7 +1250,7 @@ pub fn Select(comptime U: type) type { /// already been called and completed, or it has successfully been /// assigned a unit of concurrency. /// - /// After this is called, `wait` or `cancel` must be called before the + /// After this is called, `await` or `cancel` must be called before the /// select is deinitialized. /// /// Threadsafe. @@ -1293,12 +1293,12 @@ pub fn Select(comptime U: type) type { }; } - /// Equivalent to `wait` but requests cancelation on all remaining + /// Equivalent to `await` but requests cancelation on all remaining /// tasks owned by the select. /// /// For a description of cancelation and cancelation points, see `Future.cancel`. /// - /// It is illegal to call `wait` after this. + /// It is illegal to call `await` after this. /// /// Idempotent. Not threadsafe. pub fn cancel(s: *S) void { From 376320a5e9bfc32162c81b592784e6f54aeae62b Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Wed, 14 Jan 2026 18:15:18 -0600 Subject: [PATCH 2/3] Io.Select: do not swallow error.Canceled of task --- lib/std/Io.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 18aa1e127a..38ed8c865a 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1269,10 +1269,13 @@ pub fn Select(comptime U: type) type { args: @TypeOf(args), fn start(type_erased_context: *const anyopaque) Cancelable!void { const context: *const @This() = @ptrCast(@alignCast(type_erased_context)); - const elem = @unionInit(U, @tagName(field), @call(.auto, function, context.args)); + const raw_result = @call(.auto, function, context.args); + const elem = @unionInit(U, @tagName(field), raw_result); context.select.queue.putOneUncancelable(context.select.io, elem) catch |err| switch (err) { error.Closed => unreachable, }; + if (@typeInfo(@TypeOf(raw_result)) == .error_union) + raw_result catch |err| if (err == error.Canceled) return error.Canceled; } }; const context: Context = .{ .select = s, .args = args }; From 6b733537abec526a878c3fd2f62d7ec2386ded56 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Wed, 14 Jan 2026 18:18:29 -0600 Subject: [PATCH 3/3] Io.Select: add fn concurrent --- lib/std/Io.zig | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 38ed8c865a..f757e747ca 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1283,6 +1283,46 @@ pub fn Select(comptime U: type) type { s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&context), .of(Context), Context.start); } + /// Calls `function` with `args` concurrently. The resource spawned is + /// owned by the select. + /// + /// `function` must have return type matching the `field` field of `Union`. + /// + /// After this function returns successfully, it is guaranteed that + /// `function` has been assigned a unit of concurrency, and `await` or + /// `cancel` must be called before the select is deinitialized. + /// + /// + /// Threadsafe. + /// + /// Related: + /// * `Io.concurrent` + /// * `Group.concurrent` + pub fn concurrent( + s: *S, + comptime field: Field, + function: anytype, + args: std.meta.ArgsTuple(@TypeOf(function)), + ) ConcurrentError!void { + const Context = struct { + select: *S, + args: @TypeOf(args), + fn start(type_erased_context: *const anyopaque) Cancelable!void { + const context: *const @This() = @ptrCast(@alignCast(type_erased_context)); + const raw_result = @call(.auto, function, context.args); + const elem = @unionInit(U, @tagName(field), raw_result); + context.select.queue.putOneUncancelable(context.select.io, elem) catch |err| switch (err) { + error.Closed => unreachable, + }; + if (@typeInfo(@TypeOf(raw_result)) == .error_union) + raw_result catch |err| if (err == error.Canceled) return error.Canceled; + } + }; + const context: Context = .{ .select = s, .args = args }; + try s.io.vtable.groupConcurrent(s.io.userdata, &s.group, @ptrCast(&context), .of(Context), Context.start); + _ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic); + } + /// Blocks until another task of the select finishes. /// /// Asserts there is at least one more `outstanding` task.