authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-28 15:06:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:52-07:00
log03fd132b1ce82f767ffdb6cc886d1934b40c6071
treef243046bf40b9595edba59b49ab59686b8b03f67
parent6c794ce7bceeefceeee43a5514e633162ee946a9

std.Io: fix Group.wait unsoundness

Previously if a Group.wait was canceled, then a subsequent call to wait() or cancel() would trip an assertion in the synchronization code.

4 files changed, 33 insertions(+), 82 deletions(-)

lib/std/Io.zig+7-19
......@@ -653,8 +653,7 @@ pub const VTable = struct {
653653 context_alignment: std.mem.Alignment,
654654 start: *const fn (*Group, context: *const anyopaque) void,
655655 ) void,
656 groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) Cancelable!void,
657 groupWaitUncancelable: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
656 groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
658657 groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
659658
660659 /// Blocks until one of the futures from the list has a result ready, such
......@@ -1038,29 +1037,18 @@ pub const Group = struct {
10381037 io.vtable.groupAsync(io.userdata, g, @ptrCast((&args)[0..1]), .of(Args), TypeErased.start);
10391038 }
10401039
1041 /// Blocks until all tasks of the group finish.
1042 ///
1043 /// On success, further calls to `wait`, `waitUncancelable`, and `cancel`
1044 /// do nothing.
1045 ///
1046 /// Not threadsafe.
1047 pub fn wait(g: *Group, io: Io) Cancelable!void {
1048 const token = g.token orelse return;
1049 try io.vtable.groupWait(io.userdata, g, token);
1050 g.token = null;
1051 }
1052
1053 /// Equivalent to `wait` except uninterruptible.
1040 /// Blocks until all tasks of the group finish. During this time,
1041 /// cancellation requests propagate to all members of the group.
10541042 ///
10551043 /// Idempotent. Not threadsafe.
1056 pub fn waitUncancelable(g: *Group, io: Io) void {
1044 pub fn wait(g: *Group, io: Io) void {
10571045 const token = g.token orelse return;
10581046 g.token = null;
1059 io.vtable.groupWaitUncancelable(io.userdata, g, token);
1047 io.vtable.groupWait(io.userdata, g, token);
10601048 }
10611049
1062 /// Equivalent to `wait` but requests cancellation on all tasks owned by
1063 /// the group.
1050 /// Equivalent to `wait` but immediately requests cancellation on all
1051 /// members of the group.
10641052 ///
10651053 /// Idempotent. Not threadsafe.
10661054 pub fn cancel(g: *Group, io: Io) void {
lib/std/Io/Kqueue.zig+1-10
......@@ -859,7 +859,6 @@ pub fn io(k: *Kqueue) Io {
859859
860860 .groupAsync = groupAsync,
861861 .groupWait = groupWait,
862 .groupWaitUncancelable = groupWaitUncancelable,
863862 .groupCancel = groupCancel,
864863
865864 .mutexLock = mutexLock,
......@@ -1027,15 +1026,7 @@ fn groupAsync(
10271026 @panic("TODO");
10281027}
10291028
1030fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) Io.Cancelable!void {
1031 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1032 _ = k;
1033 _ = group;
1034 _ = token;
1035 @panic("TODO");
1036}
1037
1038fn groupWaitUncancelable(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
1029fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
10391030 const k: *Kqueue = @ptrCast(@alignCast(userdata));
10401031 _ = k;
10411032 _ = group;
lib/std/Io/Threaded.zig+23-50
......@@ -177,7 +177,6 @@ pub fn io(t: *Threaded) Io {
177177
178178 .groupAsync = groupAsync,
179179 .groupWait = groupWait,
180 .groupWaitUncancelable = groupWaitUncancelable,
181180 .groupCancel = groupCancel,
182181
183182 .mutexLock = mutexLock,
......@@ -274,7 +273,6 @@ pub fn ioBasic(t: *Threaded) Io {
274273
275274 .groupAsync = groupAsync,
276275 .groupWait = groupWait,
277 .groupWaitUncancelable = groupWaitUncancelable,
278276 .groupCancel = groupCancel,
279277
280278 .mutexLock = mutexLock,
......@@ -579,7 +577,9 @@ const GroupClosure = struct {
579577 assert(cancel_tid == .canceling);
580578 }
581579
582 syncFinish(group_state, reset_event);
580 const prev_state = group_state.fetchSub(sync_one_pending, .acq_rel);
581 assert((prev_state / sync_one_pending) > 0);
582 if (prev_state == (sync_one_pending | sync_is_waiting)) reset_event.set();
583583 }
584584
585585 fn free(gc: *GroupClosure, gpa: Allocator) void {
......@@ -602,29 +602,6 @@ const GroupClosure = struct {
602602
603603 const sync_is_waiting: usize = 1 << 0;
604604 const sync_one_pending: usize = 1 << 1;
605
606 fn syncStart(state: *std.atomic.Value(usize)) void {
607 const prev_state = state.fetchAdd(sync_one_pending, .monotonic);
608 assert((prev_state / sync_one_pending) < (std.math.maxInt(usize) / sync_one_pending));
609 }
610
611 fn syncFinish(state: *std.atomic.Value(usize), event: *ResetEvent) void {
612 const prev_state = state.fetchSub(sync_one_pending, .acq_rel);
613 assert((prev_state / sync_one_pending) > 0);
614 if (prev_state == (sync_one_pending | sync_is_waiting)) event.set();
615 }
616
617 fn syncWait(t: *Threaded, state: *std.atomic.Value(usize), event: *ResetEvent) Io.Cancelable!void {
618 const prev_state = state.fetchAdd(sync_is_waiting, .acquire);
619 assert(prev_state & sync_is_waiting == 0);
620 if ((prev_state / sync_one_pending) > 0) try event.wait(t);
621 }
622
623 fn syncWaitUncancelable(state: *std.atomic.Value(usize), event: *ResetEvent) void {
624 const prev_state = state.fetchAdd(sync_is_waiting, .acquire);
625 assert(prev_state & sync_is_waiting == 0);
626 if ((prev_state / sync_one_pending) > 0) event.waitUncancelable();
627 }
628605};
629606
630607fn groupAsync(
......@@ -686,32 +663,14 @@ fn groupAsync(
686663 // This needs to be done before unlocking the mutex to avoid a race with
687664 // the associated task finishing.
688665 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
689 GroupClosure.syncStart(group_state);
666 const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic);
667 assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending));
690668
691669 t.mutex.unlock();
692670 t.cond.signal();
693671}
694672
695fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) Io.Cancelable!void {
696 const t: *Threaded = @ptrCast(@alignCast(userdata));
697 const gpa = t.allocator;
698
699 if (builtin.single_threaded) return;
700
701 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
702 const reset_event: *ResetEvent = @ptrCast(&group.context);
703 try GroupClosure.syncWait(t, group_state, reset_event);
704
705 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
706 while (true) {
707 const gc: *GroupClosure = @fieldParentPtr("node", node);
708 const node_next = node.next;
709 gc.free(gpa);
710 node = node_next orelse break;
711 }
712}
713
714fn groupWaitUncancelable(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
673fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
715674 const t: *Threaded = @ptrCast(@alignCast(userdata));
716675 const gpa = t.allocator;
717676
......@@ -719,7 +678,19 @@ fn groupWaitUncancelable(userdata: ?*anyopaque, group: *Io.Group, token: *anyopa
719678
720679 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
721680 const reset_event: *ResetEvent = @ptrCast(&group.context);
722 GroupClosure.syncWaitUncancelable(group_state, reset_event);
681 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
682 assert(prev_state & GroupClosure.sync_is_waiting == 0);
683 if ((prev_state / GroupClosure.sync_one_pending) > 0) reset_event.wait(t) catch |err| switch (err) {
684 error.Canceled => {
685 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
686 while (true) {
687 const gc: *GroupClosure = @fieldParentPtr("node", node);
688 gc.closure.requestCancel();
689 node = node.next orelse break;
690 }
691 reset_event.waitUncancelable();
692 },
693 };
723694
724695 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
725696 while (true) {
......@@ -747,7 +718,9 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void
747718
748719 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
749720 const reset_event: *ResetEvent = @ptrCast(&group.context);
750 GroupClosure.syncWaitUncancelable(group_state, reset_event);
721 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
722 assert(prev_state & GroupClosure.sync_is_waiting == 0);
723 if ((prev_state / GroupClosure.sync_one_pending) > 0) reset_event.waitUncancelable();
751724
752725 {
753726 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
......@@ -1549,7 +1522,7 @@ fn dirAccessPosix(
15491522 .FAULT => |err| return errnoBug(err),
15501523 .IO => return error.InputOutput,
15511524 .NOMEM => return error.SystemResources,
1552 .ILSEQ => return error.BadPathName, // TODO move to wasi
1525 .ILSEQ => return error.BadPathName,
15531526 else => |err| return posix.unexpectedErrno(err),
15541527 }
15551528 }
lib/std/Io/net/HostName.zig+2-3
......@@ -280,9 +280,8 @@ pub fn connectMany(
280280 .address => |address| group.async(io, enqueueConnection, .{ address, io, results, options }),
281281 .canonical_name => continue,
282282 .end => |lookup_result| {
283 results.putOneUncancelable(io, .{
284 .end = if (group.wait(io)) lookup_result else |err| err,
285 });
283 group.wait(io);
284 results.putOneUncancelable(io, .{ .end = lookup_result });
286285 return;
287286 },
288287 } else |err| switch (err) {