authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-26 21:58:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log3c9fdf810f6f1517193786998ac5efe9b2b2c276
tree93236f61e8ac1c74b508b7299febe7440124bcf7
parentd37d795cba27c9793436449bfd783fc504c69cbb

std.Io: implement Group API


3 files changed, 102 insertions(+), 49 deletions(-)

lib/std/Io.zig+57-32
...@@ -593,18 +593,6 @@ pub const VTable = struct {...@@ -593,18 +593,6 @@ pub const VTable = struct {
593 context_alignment: std.mem.Alignment,593 context_alignment: std.mem.Alignment,
594 start: *const fn (context: *const anyopaque, result: *anyopaque) void,594 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
595 ) error{OutOfMemory}!*AnyFuture,595 ) error{OutOfMemory}!*AnyFuture,
596 /// Executes `start` asynchronously in a manner such that it cleans itself
597 /// up. This mode does not support results, await, or cancel.
598 ///
599 /// Thread-safe.
600 asyncDetached: *const fn (
601 /// Corresponds to `Io.userdata`.
602 userdata: ?*anyopaque,
603 /// Copied and then passed to `start`.
604 context: []const u8,
605 context_alignment: std.mem.Alignment,
606 start: *const fn (context: *const anyopaque) void,
607 ) void,
608 /// This function is only called when `async` returns a non-null value.596 /// This function is only called when `async` returns a non-null value.
609 ///597 ///
610 /// Thread-safe.598 /// Thread-safe.
...@@ -639,6 +627,23 @@ pub const VTable = struct {...@@ -639,6 +627,23 @@ pub const VTable = struct {
639 /// Thread-safe.627 /// Thread-safe.
640 cancelRequested: *const fn (?*anyopaque) bool,628 cancelRequested: *const fn (?*anyopaque) bool,
641629
630 /// Executes `start` asynchronously in a manner such that it cleans itself
631 /// up. This mode does not support results, await, or cancel.
632 ///
633 /// Thread-safe.
634 groupAsync: *const fn (
635 /// Corresponds to `Io.userdata`.
636 userdata: ?*anyopaque,
637 /// Owner of the spawned async task.
638 group: *Group,
639 /// Copied and then passed to `start`.
640 context: []const u8,
641 context_alignment: std.mem.Alignment,
642 start: *const fn (context: *const anyopaque) void,
643 ) void,
644 groupWait: *const fn (?*anyopaque, *Group) void,
645 groupCancel: *const fn (?*anyopaque, *Group) void,
646
642 /// Blocks until one of the futures from the list has a result ready, such647 /// Blocks until one of the futures from the list has a result ready, such
643 /// that awaiting it will not block. Returns that index.648 /// that awaiting it will not block. Returns that index.
644 select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize,649 select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize,
...@@ -751,6 +756,45 @@ pub fn Future(Result: type) type {...@@ -751,6 +756,45 @@ pub fn Future(Result: type) type {
751 };756 };
752}757}
753758
759pub const Group = struct {
760 state: usize,
761 context: ?*anyopaque,
762
763 pub const init: Group = .{ .state = 0, .context = null };
764
765 /// Calls `function` with `args` asynchronously. The resource spawned is
766 /// owned by the group.
767 ///
768 /// `function` *may* be called immediately, before `async` returns.
769 ///
770 /// After this is called, `wait` must be called before the group is
771 /// deinitialized.
772 ///
773 /// See also:
774 /// * `async`
775 /// * `concurrent`
776 pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
777 const Args = @TypeOf(args);
778 const TypeErased = struct {
779 fn start(context: *const anyopaque) void {
780 const args_casted: *const Args = @ptrCast(@alignCast(context));
781 @call(.auto, function, args_casted.*);
782 }
783 };
784 io.vtable.groupAsync(io.userdata, g, @ptrCast((&args)[0..1]), .of(Args), TypeErased.start);
785 }
786
787 /// Idempotent.
788 pub fn wait(g: *Group, io: Io) void {
789 io.vtable.groupWait(io.userdata, g);
790 }
791
792 /// Idempotent.
793 pub fn cancel(g: *Group, io: Io) void {
794 io.vtable.groupCancel(io.userdata, g);
795 }
796};
797
754pub const Mutex = if (true) struct {798pub const Mutex = if (true) struct {
755 state: State,799 state: State,
756800
...@@ -1099,7 +1143,7 @@ pub fn Queue(Elem: type) type {...@@ -1099,7 +1143,7 @@ pub fn Queue(Elem: type) type {
1099/// reusable.1143/// reusable.
1100///1144///
1101/// See also:1145/// See also:
1102/// * `asyncDetached`1146/// * `Group`
1103pub fn async(1147pub fn async(
1104 io: Io,1148 io: Io,
1105 function: anytype,1149 function: anytype,
...@@ -1159,25 +1203,6 @@ pub fn concurrent(...@@ -1159,25 +1203,6 @@ pub fn concurrent(
1159 return future;1203 return future;
1160}1204}
11611205
1162/// Calls `function` with `args` asynchronously. The resource cleans itself up
1163/// when the function returns. Does not support await, cancel, or a return value.
1164///
1165/// `function` *may* be called immediately, before `async` returns.
1166///
1167/// See also:
1168/// * `async`
1169/// * `concurrent`
1170pub fn asyncDetached(io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
1171 const Args = @TypeOf(args);
1172 const TypeErased = struct {
1173 fn start(context: *const anyopaque) void {
1174 const args_casted: *const Args = @ptrCast(@alignCast(context));
1175 @call(.auto, function, args_casted.*);
1176 }
1177 };
1178 io.vtable.asyncDetached(io.userdata, @ptrCast((&args)[0..1]), .of(Args), TypeErased.start);
1179}
1180
1181pub fn cancelRequested(io: Io) bool {1206pub fn cancelRequested(io: Io) bool {
1182 return io.vtable.cancelRequested(io.userdata);1207 return io.vtable.cancelRequested(io.userdata);
1183}1208}
lib/std/Io/Threaded.zig+43-15
...@@ -8,7 +8,6 @@ const windows = std.os.windows;...@@ -8,7 +8,6 @@ const windows = std.os.windows;
8const std = @import("../std.zig");8const std = @import("../std.zig");
9const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
10const assert = std.debug.assert;10const assert = std.debug.assert;
11const WaitGroup = std.Thread.WaitGroup;
12const posix = std.posix;11const posix = std.posix;
13const Io = std.Io;12const Io = std.Io;
1413
...@@ -101,10 +100,12 @@ pub fn io(pool: *Pool) Io {...@@ -101,10 +100,12 @@ pub fn io(pool: *Pool) Io {
101 .async = async,100 .async = async,
102 .concurrent = concurrent,101 .concurrent = concurrent,
103 .await = await,102 .await = await,
104 .asyncDetached = asyncDetached,
105 .cancel = cancel,103 .cancel = cancel,
106 .cancelRequested = cancelRequested,104 .cancelRequested = cancelRequested,
107 .select = select,105 .select = select,
106 .groupAsync = groupAsync,
107 .groupWait = groupWait,
108 .groupCancel = groupCancel,
108109
109 .mutexLock = mutexLock,110 .mutexLock = mutexLock,
110 .mutexUnlock = mutexUnlock,111 .mutexUnlock = mutexUnlock,
...@@ -279,7 +280,7 @@ fn async(...@@ -279,7 +280,7 @@ fn async(
279 .func = start,280 .func = start,
280 .context_offset = context_offset,281 .context_offset = context_offset,
281 .result_offset = result_offset,282 .result_offset = result_offset,
282 .reset_event = .{},283 .reset_event = .unset,
283 .cancel_tid = 0,284 .cancel_tid = 0,
284 .select_condition = null,285 .select_condition = null,
285 .runnable = .{286 .runnable = .{
...@@ -347,7 +348,7 @@ fn concurrent(...@@ -347,7 +348,7 @@ fn concurrent(
347 .func = start,348 .func = start,
348 .context_offset = context_offset,349 .context_offset = context_offset,
349 .result_offset = result_offset,350 .result_offset = result_offset,
350 .reset_event = .{},351 .reset_event = .unset,
351 .cancel_tid = 0,352 .cancel_tid = 0,
352 .select_condition = null,353 .select_condition = null,
353 .runnable = .{354 .runnable = .{
...@@ -385,41 +386,47 @@ fn concurrent(...@@ -385,41 +386,47 @@ fn concurrent(
385 return @ptrCast(closure);386 return @ptrCast(closure);
386}387}
387388
388const DetachedClosure = struct {389const GroupClosure = struct {
389 pool: *Pool,390 pool: *Pool,
391 group: *Io.Group,
390 func: *const fn (context: *anyopaque) void,392 func: *const fn (context: *anyopaque) void,
391 runnable: Runnable,393 runnable: Runnable,
392 context_alignment: std.mem.Alignment,394 context_alignment: std.mem.Alignment,
393 context_len: usize,395 context_len: usize,
394396
395 fn start(runnable: *Runnable) void {397 fn start(runnable: *Runnable) void {
396 const closure: *DetachedClosure = @alignCast(@fieldParentPtr("runnable", runnable));398 const closure: *GroupClosure = @alignCast(@fieldParentPtr("runnable", runnable));
397 closure.func(closure.contextPointer());399 closure.func(closure.contextPointer());
400 const group = closure.group;
398 const gpa = closure.pool.allocator;401 const gpa = closure.pool.allocator;
399 free(closure, gpa);402 free(closure, gpa);
403 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
404 const reset_event: *std.Thread.ResetEvent = @ptrCast(&group.context);
405 std.Thread.WaitGroup.finishStateless(group_state, reset_event);
400 }406 }
401407
402 fn free(closure: *DetachedClosure, gpa: Allocator) void {408 fn free(closure: *GroupClosure, gpa: Allocator) void {
403 const base: [*]align(@alignOf(DetachedClosure)) u8 = @ptrCast(closure);409 const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(closure);
404 gpa.free(base[0..contextEnd(closure.context_alignment, closure.context_len)]);410 gpa.free(base[0..contextEnd(closure.context_alignment, closure.context_len)]);
405 }411 }
406412
407 fn contextOffset(context_alignment: std.mem.Alignment) usize {413 fn contextOffset(context_alignment: std.mem.Alignment) usize {
408 return context_alignment.forward(@sizeOf(DetachedClosure));414 return context_alignment.forward(@sizeOf(GroupClosure));
409 }415 }
410416
411 fn contextEnd(context_alignment: std.mem.Alignment, context_len: usize) usize {417 fn contextEnd(context_alignment: std.mem.Alignment, context_len: usize) usize {
412 return contextOffset(context_alignment) + context_len;418 return contextOffset(context_alignment) + context_len;
413 }419 }
414420
415 fn contextPointer(closure: *DetachedClosure) [*]u8 {421 fn contextPointer(closure: *GroupClosure) [*]u8 {
416 const base: [*]u8 = @ptrCast(closure);422 const base: [*]u8 = @ptrCast(closure);
417 return base + contextOffset(closure.context_alignment);423 return base + contextOffset(closure.context_alignment);
418 }424 }
419};425};
420426
421fn asyncDetached(427fn groupAsync(
422 userdata: ?*anyopaque,428 userdata: ?*anyopaque,
429 group: *Io.Group,
423 context: []const u8,430 context: []const u8,
424 context_alignment: std.mem.Alignment,431 context_alignment: std.mem.Alignment,
425 start: *const fn (context: *const anyopaque) void,432 start: *const fn (context: *const anyopaque) void,
...@@ -428,17 +435,18 @@ fn asyncDetached(...@@ -428,17 +435,18 @@ fn asyncDetached(
428 const pool: *Pool = @ptrCast(@alignCast(userdata));435 const pool: *Pool = @ptrCast(@alignCast(userdata));
429 const cpu_count = pool.cpu_count catch 1;436 const cpu_count = pool.cpu_count catch 1;
430 const gpa = pool.allocator;437 const gpa = pool.allocator;
431 const n = DetachedClosure.contextEnd(context_alignment, context.len);438 const n = GroupClosure.contextEnd(context_alignment, context.len);
432 const closure: *DetachedClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(DetachedClosure), n) catch {439 const closure: *GroupClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(GroupClosure), n) catch {
433 return start(context.ptr);440 return start(context.ptr);
434 }));441 }));
435 closure.* = .{442 closure.* = .{
436 .pool = pool,443 .pool = pool,
444 .group = group,
437 .func = start,445 .func = start,
438 .context_alignment = context_alignment,446 .context_alignment = context_alignment,
439 .context_len = context.len,447 .context_len = context.len,
440 .runnable = .{448 .runnable = .{
441 .start = DetachedClosure.start,449 .start = GroupClosure.start,
442 .is_parallel = false,450 .is_parallel = false,
443 },451 },
444 };452 };
...@@ -466,10 +474,30 @@ fn asyncDetached(...@@ -466,10 +474,30 @@ fn asyncDetached(
466 pool.threads.appendAssumeCapacity(thread);474 pool.threads.appendAssumeCapacity(thread);
467 }475 }
468476
477 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
478 std.Thread.WaitGroup.startStateless(group_state);
479
469 pool.mutex.unlock();480 pool.mutex.unlock();
470 pool.cond.signal();481 pool.cond.signal();
471}482}
472483
484fn groupWait(userdata: ?*anyopaque, group: *Io.Group) void {
485 if (builtin.single_threaded) return;
486 const pool: *Pool = @ptrCast(@alignCast(userdata));
487 _ = pool;
488 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
489 const reset_event: *std.Thread.ResetEvent = @ptrCast(&group.context);
490 std.Thread.WaitGroup.waitStateless(group_state, reset_event);
491}
492
493fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void {
494 if (builtin.single_threaded) return;
495 const pool: *Pool = @ptrCast(@alignCast(userdata));
496 _ = pool;
497 _ = group;
498 @panic("TODO threaded group cancel");
499}
500
473fn await(501fn await(
474 userdata: ?*anyopaque,502 userdata: ?*anyopaque,
475 any_future: *Io.AnyFuture,503 any_future: *Io.AnyFuture,
...@@ -968,7 +996,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {...@@ -968,7 +996,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
968 const pool: *Pool = @ptrCast(@alignCast(userdata));996 const pool: *Pool = @ptrCast(@alignCast(userdata));
969 _ = pool;997 _ = pool;
970998
971 var reset_event: std.Thread.ResetEvent = .{};999 var reset_event: std.Thread.ResetEvent = .unset;
9721000
973 for (futures, 0..) |future, i| {1001 for (futures, 0..) |future, i| {
974 const closure: *AsyncClosure = @ptrCast(@alignCast(future));1002 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
lib/std/Io/net/HostName.zig+2-2
...@@ -653,7 +653,7 @@ pub const ResolvConf = struct {...@@ -653,7 +653,7 @@ pub const ResolvConf = struct {
653653
654 const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers();654 const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers();
655655
656 var group: Io.Group = .{};656 var group: Io.Group = .init;
657 defer group.cancel();657 defer group.cancel();
658658
659 for (queries) |query| {659 for (queries) |query| {
...@@ -702,7 +702,7 @@ test ResolvConf {...@@ -702,7 +702,7 @@ test ResolvConf {
702 .search_buffer = undefined,702 .search_buffer = undefined,
703 .search_len = 0,703 .search_len = 0,
704 .ndots = 1,704 .ndots = 1,
705 .timeout = 5,705 .timeout = .seconds(5),
706 .attempts = 2,706 .attempts = 2,
707 };707 };
708708