authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-12-30 21:46:57+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 15:45:11+00:00
logb8a09bcbd955c2d97216ffef23246054aa3fe756
tree5f53c58e6411ad5d526994c67056105f667e841b
parent2c395e326f2db4e2389af729b88bd93e76e4d27c
signaturelock-open Commit is signed but in an unrecognized format.

std.Io.Group: tweak documentation and vtable API


2 files changed, 47 insertions(+), 55 deletions(-)

lib/std/Io.zig+38-46
...@@ -631,7 +631,7 @@ pub const VTable = struct {...@@ -631,7 +631,7 @@ pub const VTable = struct {
631 /// Copied and then passed to `start`.631 /// Copied and then passed to `start`.
632 context: []const u8,632 context: []const u8,
633 context_alignment: std.mem.Alignment,633 context_alignment: std.mem.Alignment,
634 start: *const fn (*Group, context: *const anyopaque) Cancelable!void,634 start: *const fn (context: *const anyopaque) Cancelable!void,
635 ) void,635 ) void,
636 /// Thread-safe.636 /// Thread-safe.
637 groupConcurrent: *const fn (637 groupConcurrent: *const fn (
...@@ -642,7 +642,7 @@ pub const VTable = struct {...@@ -642,7 +642,7 @@ pub const VTable = struct {
642 /// Copied and then passed to `start`.642 /// Copied and then passed to `start`.
643 context: []const u8,643 context: []const u8,
644 context_alignment: std.mem.Alignment,644 context_alignment: std.mem.Alignment,
645 start: *const fn (*Group, context: *const anyopaque) Cancelable!void,645 start: *const fn (context: *const anyopaque) Cancelable!void,
646 ) ConcurrentError!void,646 ) ConcurrentError!void,
647 groupAwait: *const fn (?*anyopaque, *Group, token: *anyopaque) Cancelable!void,647 groupAwait: *const fn (?*anyopaque, *Group, token: *anyopaque) Cancelable!void,
648 groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,648 groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
...@@ -1050,40 +1050,40 @@ pub fn Future(Result: type) type {...@@ -1050,40 +1050,40 @@ pub fn Future(Result: type) type {
1050 };1050 };
1051}1051}
10521052
1053/// An unordered set of tasks which can only be awaited or canceled as a whole.
1054/// Tasks are spawned in the group with `Group.async` and `Group.concurrent`.
1055///
1056/// The resources associated with each task are *guaranteed* to be released when
1057/// the individual task returns, as opposed to when the whole group completes or
1058/// is awaited. For this reason, it is not a resource leak to have a long-lived
1059/// group which concurrent tasks are repeatedly added to. However, asynchronous
1060/// tasks are not guaranteed to run until `Group.await` or `Group.cancel` is
1061/// called, so adding async tasks to a group without ever awaiting it may leak
1062/// resources.
1053pub const Group = struct {1063pub const Group = struct {
1054 state: usize,
1055 context: ?*anyopaque,
1056 /// This value indicates whether or not a group has pending tasks. `null`1064 /// This value indicates whether or not a group has pending tasks. `null`
1057 /// means there are no pending tasks, and no resources associated with the1065 /// means there are no pending tasks, and no resources associated with the
1058 /// group, so `await` and `cancel` return immediately without calling the1066 /// group, so `await` and `cancel` return immediately without calling the
1059 /// implementation. This means that `token` must be accessed atomically to1067 /// implementation. This means that `token` must be accessed atomically to
1060 /// avoid racing with the check in `await` and `cancel`.1068 /// avoid racing with the check in `await` and `cancel`.
1061 token: std.atomic.Value(?*anyopaque),1069 token: std.atomic.Value(?*anyopaque),
1070 /// This value is available for the implementation to use as it wishes.
1071 state: usize,
10621072
1063 pub const init: Group = .{ .state = 0, .context = null, .token = .init(null) };1073 pub const init: Group = .{ .token = .init(null), .state = 0 };
10641074
1065 /// Calls `function` with `args` asynchronously. The resource spawned is1075 /// Equivalent to `Io.async`, except the task is spawned in this `Group`
1066 /// owned by the group.1076 /// instead of becoming associated with a `Future`.
1067 ///
1068 /// `function` *may* be called immediately, before `async` returns.
1069 ///1077 ///
1070 /// When this function returns, it is guaranteed that `function` has1078 /// The return type of `function` must be coercible to `Cancelable!void`.
1071 /// already been called and completed, or it has successfully been assigned
1072 /// a unit of concurrency.
1073 ///1079 ///
1074 /// After this is called, `await` or `cancel` must be called before the1080 /// Once this function is called, there are resources associated with the
1075 /// group is deinitialized.1081 /// group. To release those resources, `Group.await` or `Group.cancel` must
1076 ///1082 /// eventually be called.
1077 /// Threadsafe.
1078 ///
1079 /// See also:
1080 /// * `concurrent`
1081 /// * `Io.async`
1082 pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {1083 pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
1083 const Args = @TypeOf(args);1084 const Args = @TypeOf(args);
1084 const TypeErased = struct {1085 const TypeErased = struct {
1085 fn start(group: *Group, context: *const anyopaque) Cancelable!void {1086 fn start(context: *const anyopaque) Cancelable!void {
1086 _ = group;
1087 const args_casted: *const Args = @ptrCast(@alignCast(context));1087 const args_casted: *const Args = @ptrCast(@alignCast(context));
1088 return @call(.auto, function, args_casted.*);1088 return @call(.auto, function, args_casted.*);
1089 }1089 }
...@@ -1091,27 +1091,18 @@ pub const Group = struct {...@@ -1091,27 +1091,18 @@ pub const Group = struct {
1091 io.vtable.groupAsync(io.userdata, g, @ptrCast(&args), .of(Args), TypeErased.start);1091 io.vtable.groupAsync(io.userdata, g, @ptrCast(&args), .of(Args), TypeErased.start);
1092 }1092 }
10931093
1094 /// Calls `function` with `args`, such that the function is not guaranteed1094 /// Equivalent to `Io.concurrent`, except the task is spawned in this
1095 /// to have returned until `await` is called, allowing the caller to1095 /// `Group` instead of becoming associated with a `Future`.
1096 /// progress while waiting for any `Io` operations.
1097 ///
1098 /// The resource spawned is owned by the group; after this is called,
1099 /// `await` or `cancel` must be called before the group is deinitialized.
1100 ///
1101 /// This has stronger guarantee than `async`, placing restrictions on what kind
1102 /// of `Io` implementations are supported. By calling `async` instead, one
1103 /// allows, for example, stackful single-threaded blocking I/O.
1104 ///1096 ///
1105 /// Threadsafe.1097 /// The return type of `function` must be coercible to `Cancelable!void`.
1106 ///1098 ///
1107 /// See also:1099 /// Once this function is called, there are resources associated with the
1108 /// * `async`1100 /// group. To release those resources, `Group.await` or `Group.cancel` must
1109 /// * `Io.concurrent`1101 /// eventually be called.
1110 pub fn concurrent(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) ConcurrentError!void {1102 pub fn concurrent(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) ConcurrentError!void {
1111 const Args = @TypeOf(args);1103 const Args = @TypeOf(args);
1112 const TypeErased = struct {1104 const TypeErased = struct {
1113 fn start(group: *Group, context: *const anyopaque) Cancelable!void {1105 fn start(context: *const anyopaque) Cancelable!void {
1114 _ = group;
1115 const args_casted: *const Args = @ptrCast(@alignCast(context));1106 const args_casted: *const Args = @ptrCast(@alignCast(context));
1116 return @call(.auto, function, args_casted.*);1107 return @call(.auto, function, args_casted.*);
1117 }1108 }
...@@ -1263,19 +1254,20 @@ pub fn Select(comptime U: type) type {...@@ -1263,19 +1254,20 @@ pub fn Select(comptime U: type) type {
1263 function: anytype,1254 function: anytype,
1264 args: std.meta.ArgsTuple(@TypeOf(function)),1255 args: std.meta.ArgsTuple(@TypeOf(function)),
1265 ) void {1256 ) void {
1266 const Args = @TypeOf(args);1257 const Context = struct {
1267 const TypeErased = struct {1258 select: *S,
1268 fn start(group: *Group, context: *const anyopaque) Cancelable!void {1259 args: @TypeOf(args),
1269 const args_casted: *const Args = @ptrCast(@alignCast(context));1260 fn start(type_erased_context: *const anyopaque) Cancelable!void {
1270 const unerased_select: *S = @fieldParentPtr("group", group);1261 const context: *const @This() = @ptrCast(@alignCast(type_erased_context));
1271 const elem = @unionInit(U, @tagName(field), @call(.auto, function, args_casted.*));1262 const elem = @unionInit(U, @tagName(field), @call(.auto, function, context.args));
1272 unerased_select.queue.putOneUncancelable(unerased_select.io, elem) catch |err| switch (err) {1263 context.select.queue.putOneUncancelable(context.select.io, elem) catch |err| switch (err) {
1273 error.Closed => unreachable,1264 error.Closed => unreachable,
1274 };1265 };
1275 }1266 }
1276 };1267 };
1268 const context: Context = .{ .select = s, .args = args };
1277 _ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic);1269 _ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic);
1278 s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&args), .of(Args), TypeErased.start);1270 s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&context), .of(Context), Context.start);
1279 }1271 }
12801272
1281 /// Blocks until another task of the select finishes.1273 /// Blocks until another task of the select finishes.
lib/std/Io/Threaded.zig+9-9
...@@ -182,7 +182,7 @@ const Group = struct {...@@ -182,7 +182,7 @@ const Group = struct {
182 const Task = struct {182 const Task = struct {
183 runnable: Runnable,183 runnable: Runnable,
184 group: *Io.Group,184 group: *Io.Group,
185 func: *const fn (*Io.Group, context: *const anyopaque) void,185 func: *const fn (context: *const anyopaque) void,
186 context_alignment: Alignment,186 context_alignment: Alignment,
187 alloc_len: usize,187 alloc_len: usize,
188188
...@@ -192,7 +192,7 @@ const Group = struct {...@@ -192,7 +192,7 @@ const Group = struct {
192 group: Group,192 group: Group,
193 context: []const u8,193 context: []const u8,
194 context_alignment: Alignment,194 context_alignment: Alignment,
195 func: *const fn (*Io.Group, context: *const anyopaque) void,195 func: *const fn (context: *const anyopaque) void,
196 ) Allocator.Error!*Task {196 ) Allocator.Error!*Task {
197 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task);197 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task);
198 const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment);198 const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment);
...@@ -247,7 +247,7 @@ const Group = struct {...@@ -247,7 +247,7 @@ const Group = struct {
247 }, .monotonic);247 }, .monotonic);
248 }248 }
249249
250 assertGroupResult(task.func(group.ptr, task.contextPointer()));250 assertGroupResult(task.func(task.contextPointer()));
251251
252 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);252 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
253 const old_status = group.status().fetchSub(.{253 const old_status = group.status().fetchSub(.{
...@@ -1707,16 +1707,16 @@ fn groupAsync(...@@ -1707,16 +1707,16 @@ fn groupAsync(
1707 type_erased: *Io.Group,1707 type_erased: *Io.Group,
1708 context: []const u8,1708 context: []const u8,
1709 context_alignment: Alignment,1709 context_alignment: Alignment,
1710 start: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,1710 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
1711) void {1711) void {
1712 const t: *Threaded = @ptrCast(@alignCast(userdata));1712 const t: *Threaded = @ptrCast(@alignCast(userdata));
1713 const g: Group = .{ .ptr = type_erased };1713 const g: Group = .{ .ptr = type_erased };
17141714
1715 if (builtin.single_threaded) return start(g.ptr, context.ptr) catch unreachable;1715 if (builtin.single_threaded) return start(context.ptr) catch unreachable;
17161716
1717 const gpa = t.allocator;1717 const gpa = t.allocator;
1718 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {1718 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {
1719 error.OutOfMemory => return t.assertGroupResult(start(g.ptr, context.ptr)),1719 error.OutOfMemory => return t.assertGroupResult(start(context.ptr)),
1720 };1720 };
17211721
1722 t.mutex.lock();1722 t.mutex.lock();
...@@ -1726,7 +1726,7 @@ fn groupAsync(...@@ -1726,7 +1726,7 @@ fn groupAsync(
1726 if (busy_count >= @intFromEnum(t.async_limit)) {1726 if (busy_count >= @intFromEnum(t.async_limit)) {
1727 t.mutex.unlock();1727 t.mutex.unlock();
1728 task.destroy(gpa);1728 task.destroy(gpa);
1729 return t.assertGroupResult(start(g.ptr, context.ptr));1729 return t.assertGroupResult(start(context.ptr));
1730 }1730 }
17311731
1732 t.busy_count = busy_count + 1;1732 t.busy_count = busy_count + 1;
...@@ -1739,7 +1739,7 @@ fn groupAsync(...@@ -1739,7 +1739,7 @@ fn groupAsync(
1739 t.busy_count = busy_count;1739 t.busy_count = busy_count;
1740 t.mutex.unlock();1740 t.mutex.unlock();
1741 task.destroy(gpa);1741 task.destroy(gpa);
1742 return t.assertGroupResult(start(g.ptr, context.ptr));1742 return t.assertGroupResult(start(context.ptr));
1743 };1743 };
1744 thread.detach();1744 thread.detach();
1745 }1745 }
...@@ -1782,7 +1782,7 @@ fn groupConcurrent(...@@ -1782,7 +1782,7 @@ fn groupConcurrent(
1782 type_erased: *Io.Group,1782 type_erased: *Io.Group,
1783 context: []const u8,1783 context: []const u8,
1784 context_alignment: Alignment,1784 context_alignment: Alignment,
1785 start: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,1785 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
1786) Io.ConcurrentError!void {1786) Io.ConcurrentError!void {
1787 if (builtin.single_threaded) return error.ConcurrencyUnavailable;1787 if (builtin.single_threaded) return error.ConcurrencyUnavailable;
17881788