authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-29 17:15:58-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-29 22:47:34-08:00
log2adfd4d107f071f91608bef22c7e91b1a9a93470
treeef9da0bd175a2b4ddd12842e44df243606f353bf
parentf862762f091637ad0d17c4735226c0c477c2cb3e

std.Io: fix and improve Group API

Rename `wait` to `await` to be consistent with Future API. The convention here is that this set of functionality goes together: * async/concurrent * await/cancel Also rename Select `wait` to `await` for the same reason. `Group.await` now can return `error.Canceled`. Furthermore, `Group.await` does not auto-propagate cancelation. Instead, users should follow the pattern of `defer group.cancel(io);` after initialization, and doing `try group.await(io);` at the end of the success path. Advanced logic can choose to do something other than this pattern in the event of cancelation. Additionally, fixes a bug in `std.Io.Threaded` future await, in which it swallowed an `error.Canceled`. Now if a task is canceled while awaiting a future, after propagating the cancel request, it also recancels, meaning that the awaiting task will properly detect its own cancelation at the next cancelation point. Furthermore, fixes a bug in the compiler where `error.Canceled` was being swallowed in `dispatchPrelinkWork`. Finally, fixes std.crypto code that inappropriately used `catch unreachable` in response to cancelation without even so much as a comment explaining why it was believed to be unreachable. Now, those functions have `error.Canceled` in the error set and propagate cancelation properly. With this way of doing things, `Group.await` has a nice property: even if all tasks in the group are CPU bound and without cancelation points, the `Group.await` can still be canceled. In such case, the task that was waiting for `await` wakes up with a chance to do some more resource cleanup tasks, such as canceling more things, before entering the deferred `Group.cancel` call at which point it has to suspend until the canceled but uninterruptible CPU bound tasks complete. closes #30601

16 files changed, 108 insertions(+), 78 deletions(-)

lib/compiler/build_runner.zig+5-3
...@@ -748,7 +748,7 @@ fn runStepNames(...@@ -748,7 +748,7 @@ fn runStepNames(
748 defer step_prog.end();748 defer step_prog.end();
749749
750 var group: Io.Group = .init;750 var group: Io.Group = .init;
751 defer group.wait(io);751 defer group.cancel(io);
752752
753 // Here we spawn the initial set of tasks with a nice heuristic -753 // Here we spawn the initial set of tasks with a nice heuristic -
754 // dependency order. Each worker when it finishes a step will then754 // dependency order. Each worker when it finishes a step will then
...@@ -760,6 +760,8 @@ fn runStepNames(...@@ -760,6 +760,8 @@ fn runStepNames(
760760
761 group.async(io, workerMakeOneStep, .{ &group, b, step, step_prog, run });761 group.async(io, workerMakeOneStep, .{ &group, b, step, step_prog, run });
762 }762 }
763
764 try group.await(io);
763 }765 }
764766
765 assert(run.memory_blocked_steps.items.len == 0);767 assert(run.memory_blocked_steps.items.len == 0);
...@@ -820,7 +822,7 @@ fn runStepNames(...@@ -820,7 +822,7 @@ fn runStepNames(
820 // * Memory-mapping to share data between the fuzzer and build runner.822 // * Memory-mapping to share data between the fuzzer and build runner.
821 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving823 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
822 // many addresses to source locations).824 // many addresses to source locations).
823 .windows => fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}),825 .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),
824 else => {},826 else => {},
825 }827 }
826 if (@bitSizeOf(usize) != 64) {828 if (@bitSizeOf(usize) != 64) {
...@@ -843,7 +845,7 @@ fn runStepNames(...@@ -843,7 +845,7 @@ fn runStepNames(
843 step_stack.keys(),845 step_stack.keys(),
844 parent_prog_node,846 parent_prog_node,
845 mode,847 mode,
846 ) catch |err| fatal("failed to start fuzzer: {s}", .{@errorName(err)});848 ) catch |err| fatal("failed to start fuzzer: {t}", .{err});
847 defer f.deinit();849 defer f.deinit();
848850
849 f.start();851 f.start();
lib/std/Build/Fuzz.zig+3-3
...@@ -78,7 +78,7 @@ pub fn init(...@@ -78,7 +78,7 @@ pub fn init(
78 all_steps: []const *Build.Step,78 all_steps: []const *Build.Step,
79 root_prog_node: std.Progress.Node,79 root_prog_node: std.Progress.Node,
80 mode: Mode,80 mode: Mode,
81) Allocator.Error!Fuzz {81) error{ OutOfMemory, Canceled }!Fuzz {
82 const run_steps: []const *Step.Run = steps: {82 const run_steps: []const *Step.Run = steps: {
83 var steps: std.ArrayList(*Step.Run) = .empty;83 var steps: std.ArrayList(*Step.Run) = .empty;
84 defer steps.deinit(gpa);84 defer steps.deinit(gpa);
...@@ -98,7 +98,7 @@ pub fn init(...@@ -98,7 +98,7 @@ pub fn init(
98 if (steps.items.len == 0) fatal("no fuzz tests found", .{});98 if (steps.items.len == 0) fatal("no fuzz tests found", .{});
99 rebuild_node.setEstimatedTotalItems(steps.items.len);99 rebuild_node.setEstimatedTotalItems(steps.items.len);
100 const run_steps = try gpa.dupe(*Step.Run, steps.items);100 const run_steps = try gpa.dupe(*Step.Run, steps.items);
101 rebuild_group.wait(io);101 try rebuild_group.await(io);
102 break :steps run_steps;102 break :steps run_steps;
103 };103 };
104 errdefer gpa.free(run_steps);104 errdefer gpa.free(run_steps);
...@@ -517,7 +517,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {...@@ -517,7 +517,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {
517 assert(fuzz.mode == .limit);517 assert(fuzz.mode == .limit);
518 const io = fuzz.io;518 const io = fuzz.io;
519519
520 fuzz.group.wait(io);520 fuzz.group.awaitUncancelable(io);
521 fuzz.group = .init;521 fuzz.group = .init;
522522
523 std.debug.print("======= FUZZING REPORT =======\n", .{});523 std.debug.print("======= FUZZING REPORT =======\n", .{});
lib/std/Io.zig+23-12
...@@ -436,7 +436,7 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -436,7 +436,7 @@ pub fn Poller(comptime StreamEnum: type) type {
436 // Cancel the pending read into the FIFO.436 // Cancel the pending read into the FIFO.
437 _ = windows.kernel32.CancelIo(handle);437 _ = windows.kernel32.CancelIo(handle);
438438
439 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.439 // We have to wait for the handle to be signalled, i.e. for the cancelation to complete.
440 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {440 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
441 windows.WAIT_OBJECT_0 => {},441 windows.WAIT_OBJECT_0 => {},
442 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),442 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
...@@ -644,7 +644,7 @@ pub const VTable = struct {...@@ -644,7 +644,7 @@ pub const VTable = struct {
644 context_alignment: std.mem.Alignment,644 context_alignment: std.mem.Alignment,
645 start: *const fn (*Group, context: *const anyopaque) void,645 start: *const fn (*Group, context: *const anyopaque) void,
646 ) ConcurrentError!void,646 ) ConcurrentError!void,
647 groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) 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,
649649
650 recancel: *const fn (?*anyopaque) void,650 recancel: *const fn (?*anyopaque) void,
...@@ -1023,7 +1023,7 @@ pub fn Future(Result: type) type {...@@ -1023,7 +1023,7 @@ pub fn Future(Result: type) type {
1023 any_future: ?*AnyFuture,1023 any_future: ?*AnyFuture,
1024 result: Result,1024 result: Result,
10251025
1026 /// Equivalent to `await` but places a cancellation request. This causes the task to receive1026 /// Equivalent to `await` but places a cancelation request. This causes the task to receive
1027 /// `error.Canceled` from its next "cancelation point" (if any). A cancelation point is a1027 /// `error.Canceled` from its next "cancelation point" (if any). A cancelation point is a
1028 /// call to a function in `Io` which can return `error.Canceled`.1028 /// call to a function in `Io` which can return `error.Canceled`.
1029 ///1029 ///
...@@ -1071,7 +1071,7 @@ pub const Group = struct {...@@ -1071,7 +1071,7 @@ pub const Group = struct {
1071 /// already been called and completed, or it has successfully been assigned1071 /// already been called and completed, or it has successfully been assigned
1072 /// a unit of concurrency.1072 /// a unit of concurrency.
1073 ///1073 ///
1074 /// After this is called, `wait` or `cancel` must be called before the1074 /// After this is called, `await` or `cancel` must be called before the
1075 /// group is deinitialized.1075 /// group is deinitialized.
1076 ///1076 ///
1077 /// Threadsafe.1077 /// Threadsafe.
...@@ -1092,11 +1092,11 @@ pub const Group = struct {...@@ -1092,11 +1092,11 @@ pub const Group = struct {
1092 }1092 }
10931093
1094 /// Calls `function` with `args`, such that the function is not guaranteed1094 /// Calls `function` with `args`, such that the function is not guaranteed
1095 /// to have returned until `wait` is called, allowing the caller to1095 /// to have returned until `await` is called, allowing the caller to
1096 /// progress while waiting for any `Io` operations.1096 /// progress while waiting for any `Io` operations.
1097 ///1097 ///
1098 /// The resource spawned is owned by the group; after this is called,1098 /// The resource spawned is owned by the group; after this is called,
1099 /// `wait` or `cancel` must be called before the group is deinitialized.1099 /// `await` or `cancel` must be called before the group is deinitialized.
1100 ///1100 ///
1101 /// This has stronger guarantee than `async`, placing restrictions on what kind1101 /// This has stronger guarantee than `async`, placing restrictions on what kind
1102 /// of `Io` implementations are supported. By calling `async` instead, one1102 /// of `Io` implementations are supported. By calling `async` instead, one
...@@ -1120,20 +1120,31 @@ pub const Group = struct {...@@ -1120,20 +1120,31 @@ pub const Group = struct {
1120 }1120 }
11211121
1122 /// Blocks until all tasks of the group finish. During this time,1122 /// Blocks until all tasks of the group finish. During this time,
1123 /// cancellation requests propagate to all members of the group.1123 /// cancelation requests propagate to all members of the group.
1124 ///1124 ///
1125 /// Idempotent. Not threadsafe.1125 /// Idempotent. Not threadsafe.
1126 ///1126 ///
1127 /// It is safe to call this function concurrently with `Group.async` or1127 /// It is safe to call this function concurrently with `Group.async` or
1128 /// `Group.concurrent`, provided that the group does not complete until1128 /// `Group.concurrent`, provided that the group does not complete until
1129 /// the call to `Group.async` or `Group.concurrent` returns.1129 /// the call to `Group.async` or `Group.concurrent` returns.
1130 pub fn wait(g: *Group, io: Io) void {1130 pub fn await(g: *Group, io: Io) Cancelable!void {
1131 const token = g.token.load(.acquire) orelse return;1131 const token = g.token.load(.acquire) orelse return;
1132 io.vtable.groupWait(io.userdata, g, token);1132 try io.vtable.groupAwait(io.userdata, g, token);
1133 assert(g.token.raw == null);1133 assert(g.token.raw == null);
1134 }1134 }
11351135
1136 /// Equivalent to `wait` but immediately requests cancellation on all1136 /// Equivalent to `await` but temporarily blocks cancelation while waiting.
1137 pub fn awaitUncancelable(g: *Group, io: Io) void {
1138 const token = g.token.load(.acquire) orelse return;
1139 const prev = swapCancelProtection(io, .blocked);
1140 defer _ = swapCancelProtection(io, prev);
1141 io.vtable.groupAwait(io.userdata, g, token) catch |err| switch (err) {
1142 error.Canceled => unreachable,
1143 };
1144 assert(g.token.raw == null);
1145 }
1146
1147 /// Equivalent to `await` but immediately requests cancelation on all
1137 /// members of the group.1148 /// members of the group.
1138 ///1149 ///
1139 /// For a description of cancelation and cancelation points, see `Future.cancel`.1150 /// For a description of cancelation and cancelation points, see `Future.cancel`.
...@@ -1272,7 +1283,7 @@ pub fn Select(comptime U: type) type {...@@ -1272,7 +1283,7 @@ pub fn Select(comptime U: type) type {
1272 /// Asserts there is at least one more `outstanding` task.1283 /// Asserts there is at least one more `outstanding` task.
1273 ///1284 ///
1274 /// Not threadsafe.1285 /// Not threadsafe.
1275 pub fn wait(s: *S) Cancelable!U {1286 pub fn await(s: *S) Cancelable!U {
1276 s.outstanding -= 1;1287 s.outstanding -= 1;
1277 return s.queue.getOne(s.io) catch |err| switch (err) {1288 return s.queue.getOne(s.io) catch |err| switch (err) {
1278 error.Canceled => |e| return e,1289 error.Canceled => |e| return e,
...@@ -1280,7 +1291,7 @@ pub fn Select(comptime U: type) type {...@@ -1280,7 +1291,7 @@ pub fn Select(comptime U: type) type {
1280 };1291 };
1281 }1292 }
12821293
1283 /// Equivalent to `wait` but requests cancellation on all remaining1294 /// Equivalent to `wait` but requests cancelation on all remaining
1284 /// tasks owned by the select.1295 /// tasks owned by the select.
1285 ///1296 ///
1286 /// For a description of cancelation and cancelation points, see `Future.cancel`.1297 /// For a description of cancelation and cancelation points, see `Future.cancel`.
lib/std/Io/Threaded.zig+12-13
...@@ -795,7 +795,7 @@ pub fn io(t: *Threaded) Io {...@@ -795,7 +795,7 @@ pub fn io(t: *Threaded) Io {
795795
796 .groupAsync = groupAsync,796 .groupAsync = groupAsync,
797 .groupConcurrent = groupConcurrent,797 .groupConcurrent = groupConcurrent,
798 .groupWait = groupWait,798 .groupAwait = groupAwait,
799 .groupCancel = groupCancel,799 .groupCancel = groupCancel,
800800
801 .recancel = recancel,801 .recancel = recancel,
...@@ -933,7 +933,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -933,7 +933,7 @@ pub fn ioBasic(t: *Threaded) Io {
933933
934 .groupAsync = groupAsync,934 .groupAsync = groupAsync,
935 .groupConcurrent = groupConcurrent,935 .groupConcurrent = groupConcurrent,
936 .groupWait = groupWait,936 .groupAwait = groupAwait,
937 .groupCancel = groupCancel,937 .groupCancel = groupCancel,
938938
939 .recancel = recancel,939 .recancel = recancel,
...@@ -1166,6 +1166,7 @@ const AsyncClosure = struct {...@@ -1166,6 +1166,7 @@ const AsyncClosure = struct {
1166 error.Canceled => {1166 error.Canceled => {
1167 ac.closure.requestCancel(t);1167 ac.closure.requestCancel(t);
1168 ac.event.waitUncancelable(ioBasic(t));1168 ac.event.waitUncancelable(ioBasic(t));
1169 recancel(t);
1169 },1170 },
1170 };1171 };
1171 @memcpy(result, ac.resultPointer()[0..result.len]);1172 @memcpy(result, ac.resultPointer()[0..result.len]);
...@@ -1452,7 +1453,7 @@ fn groupConcurrent(...@@ -1452,7 +1453,7 @@ fn groupConcurrent(
1452 t.cond.signal();1453 t.cond.signal();
1453}1454}
14541455
1455fn groupWait(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) void {1456fn groupAwait(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void {
1456 const t: *Threaded = @ptrCast(@alignCast(userdata));1457 const t: *Threaded = @ptrCast(@alignCast(userdata));
1457 const gpa = t.allocator;1458 const gpa = t.allocator;
14581459
...@@ -1464,16 +1465,14 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque)...@@ -1464,16 +1465,14 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque)
1464 const event: *Io.Event = @ptrCast(&group.context);1465 const event: *Io.Event = @ptrCast(&group.context);
1465 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);1466 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
1466 assert(prev_state & GroupClosure.sync_is_waiting == 0);1467 assert(prev_state & GroupClosure.sync_is_waiting == 0);
1467 if ((prev_state / GroupClosure.sync_one_pending) > 0) event.wait(ioBasic(t)) catch |err| switch (err) {1468 {
1468 error.Canceled => {1469 errdefer _ = group_state.fetchSub(GroupClosure.sync_is_waiting, .monotonic);
1469 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.load(.monotonic)));1470 // This event.wait can return error.Canceled, in which case this logic does
1470 while (it) |node| : (it = node.next) {1471 // *not* propagate cancel requests to each group member. Instead, the user
1471 const gc: *GroupClosure = @fieldParentPtr("node", node);1472 // code will likely do this with a defered call to groupCancel, or,
1472 gc.closure.requestCancel(t);1473 // intentionally not do this.
1473 }1474 if ((prev_state / GroupClosure.sync_one_pending) > 0) try event.wait(ioBasic(t));
1474 event.waitUncancelable(ioBasic(t));1475 }
1475 },
1476 };
14771476
1478 // Since the group has now finished, it's illegal to add more tasks to it until we return. It's1477 // Since the group has now finished, it's illegal to add more tasks to it until we return. It's
1479 // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only1478 // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only
lib/std/Io/Threaded/test.zig+1-1
...@@ -124,7 +124,7 @@ test "Group.async context alignment" {...@@ -124,7 +124,7 @@ test "Group.async context alignment" {
124 var group: std.Io.Group = .init;124 var group: std.Io.Group = .init;
125 var result: ByteArray512 = undefined;125 var result: ByteArray512 = undefined;
126 group.async(io, concatByteArraysResultPtr, .{ a, b, &result });126 group.async(io, concatByteArraysResultPtr, .{ a, b, &result });
127 group.wait(io);127 group.awaitUncancelable(io);
128 try std.testing.expectEqualSlices(u8, &expected.x, &result.x);128 try std.testing.expectEqualSlices(u8, &expected.x, &result.x);
129}129}
130130
lib/std/Io/net/HostName.zig+1-1
...@@ -289,7 +289,7 @@ pub fn connectMany(...@@ -289,7 +289,7 @@ pub fn connectMany(
289 } else |err| switch (err) {289 } else |err| switch (err) {
290 error.Canceled => |e| return e,290 error.Canceled => |e| return e,
291 error.Closed => {291 error.Closed => {
292 group.wait(io);292 try group.await(io);
293 return lookup_future.await(io);293 return lookup_future.await(io);
294 },294 },
295 }295 }
lib/std/Io/test.zig+2-2
...@@ -194,7 +194,7 @@ test "Group" {...@@ -194,7 +194,7 @@ test "Group" {
194 group.async(io, count, .{ 1, 10, &results[0] });194 group.async(io, count, .{ 1, 10, &results[0] });
195 group.async(io, count, .{ 20, 30, &results[1] });195 group.async(io, count, .{ 20, 30, &results[1] });
196196
197 group.wait(io);197 group.awaitUncancelable(io);
198198
199 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);199 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
200}200}
...@@ -249,7 +249,7 @@ test "Group concurrent" {...@@ -249,7 +249,7 @@ test "Group concurrent" {
249 },249 },
250 };250 };
251251
252 group.wait(io);252 try group.await(io);
253253
254 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);254 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
255}255}
lib/std/crypto.zig+1-1
...@@ -184,7 +184,7 @@ pub const pwhash = struct {...@@ -184,7 +184,7 @@ pub const pwhash = struct {
184184
185 pub const Error = HasherError || error{AllocatorRequired};185 pub const Error = HasherError || error{AllocatorRequired};
186 pub const HasherError = KdfError || phc_format.Error;186 pub const HasherError = KdfError || phc_format.Error;
187 pub const KdfError = errors.Error || std.mem.Allocator.Error || std.Thread.SpawnError;187 pub const KdfError = errors.Error || std.mem.Allocator.Error || std.Thread.SpawnError || std.Io.Cancelable;
188188
189 pub const argon2 = @import("crypto/argon2.zig");189 pub const argon2 = @import("crypto/argon2.zig");
190 pub const bcrypt = @import("crypto/bcrypt.zig");190 pub const bcrypt = @import("crypto/bcrypt.zig");
lib/std/crypto/argon2.zig+16-14
...@@ -2,9 +2,9 @@...@@ -2,9 +2,9 @@
2// https://github.com/golang/crypto/tree/master/argon22// https://github.com/golang/crypto/tree/master/argon2
3// https://github.com/P-H-C/phc-winner-argon23// https://github.com/P-H-C/phc-winner-argon2
44
5const std = @import("std");
6const builtin = @import("builtin");5const builtin = @import("builtin");
76
7const std = @import("std");
8const blake2 = crypto.hash.blake2;8const blake2 = crypto.hash.blake2;
9const crypto = std.crypto;9const crypto = std.crypto;
10const Io = std.Io;10const Io = std.Io;
...@@ -53,23 +53,24 @@ pub const Mode = enum {...@@ -53,23 +53,24 @@ pub const Mode = enum {
53pub const Params = struct {53pub const Params = struct {
54 const Self = @This();54 const Self = @This();
5555
56 /// A [t]ime cost, which defines the amount of computation realized and therefore the execution56 /// Time cost, which defines the amount of computation realized and therefore the execution
57 /// time, given in number of iterations.57 /// time, given in number of iterations.
58 t: u32,58 t: u32,
5959
60 /// A [m]emory cost, which defines the memory usage, given in kibibytes.60 /// Memory cost, which defines the memory usage, given in kibibytes.
61 m: u32,61 m: u32,
6262
63 /// A [p]arallelism degree, which defines the number of parallel threads.63 /// Parallelism degree, which defines the number of independent tasks,
64 /// to be multiplexed onto threads when possible.
64 p: u24,65 p: u24,
6566
66 /// The [secret] parameter, which is used for keyed hashing. This allows a secret key to be input67 /// The secret parameter, which is used for keyed hashing. This allows a secret key to be input
67 /// at hashing time (from some external location) and be folded into the value of the hash. This68 /// at hashing time (from some external location) and be folded into the value of the hash. This
68 /// means that even if your salts and hashes are compromised, an attacker cannot brute-force to69 /// means that even if your salts and hashes are compromised, an attacker cannot brute-force to
69 /// find the password without the key.70 /// find the password without the key.
70 secret: ?[]const u8 = null,71 secret: ?[]const u8 = null,
7172
72 /// The [ad] parameter, which is used to fold any additional data into the hash value. Functionally,73 /// The ad parameter, which is used to fold any additional data into the hash value. Functionally,
73 /// this behaves almost exactly like the secret or salt parameters; the ad parameter is folding74 /// this behaves almost exactly like the secret or salt parameters; the ad parameter is folding
74 /// into the value of the hash. However, this parameter is used for different data. The salt75 /// into the value of the hash. However, this parameter is used for different data. The salt
75 /// should be a random string stored alongside your password. The secret should be a random key76 /// should be a random string stored alongside your password. The secret should be a random key
...@@ -209,18 +210,18 @@ fn processBlocks(...@@ -209,18 +210,18 @@ fn processBlocks(
209 threads: u24,210 threads: u24,
210 mode: Mode,211 mode: Mode,
211 io: Io,212 io: Io,
212) void {213) Io.Cancelable!void {
213 const lanes = memory / threads;214 const lanes = memory / threads;
214 const segments = lanes / sync_points;215 const segments = lanes / sync_points;
215216
216 if (builtin.single_threaded or threads == 1) {217 if (builtin.single_threaded or threads == 1) {
217 processBlocksSt(blocks, time, memory, threads, mode, lanes, segments);218 processBlocksSync(blocks, time, memory, threads, mode, lanes, segments);
218 } else {219 } else {
219 processBlocksMt(blocks, time, memory, threads, mode, lanes, segments, io);220 try processBlocksAsync(blocks, time, memory, threads, mode, lanes, segments, io);
220 }221 }
221}222}
222223
223fn processBlocksSt(224fn processBlocksSync(
224 blocks: *Blocks,225 blocks: *Blocks,
225 time: u32,226 time: u32,
226 memory: u32,227 memory: u32,
...@@ -241,7 +242,7 @@ fn processBlocksSt(...@@ -241,7 +242,7 @@ fn processBlocksSt(
241 }242 }
242}243}
243244
244fn processBlocksMt(245fn processBlocksAsync(
245 blocks: *Blocks,246 blocks: *Blocks,
246 time: u32,247 time: u32,
247 memory: u32,248 memory: u32,
...@@ -250,19 +251,20 @@ fn processBlocksMt(...@@ -250,19 +251,20 @@ fn processBlocksMt(
250 lanes: u32,251 lanes: u32,
251 segments: u32,252 segments: u32,
252 io: Io,253 io: Io,
253) void {254) Io.Cancelable!void {
254 var n: u32 = 0;255 var n: u32 = 0;
255 while (n < time) : (n += 1) {256 while (n < time) : (n += 1) {
256 var slice: u32 = 0;257 var slice: u32 = 0;
257 while (slice < sync_points) : (slice += 1) {258 while (slice < sync_points) : (slice += 1) {
258 var group: Io.Group = .init;259 var group: Io.Group = .init;
260 defer group.cancel(io);
259 var lane: u24 = 0;261 var lane: u24 = 0;
260 while (lane < threads) : (lane += 1) {262 while (lane < threads) : (lane += 1) {
261 group.async(io, processSegment, .{263 group.async(io, processSegment, .{
262 blocks, time, memory, threads, mode, lanes, segments, n, slice, lane,264 blocks, time, memory, threads, mode, lanes, segments, n, slice, lane,
263 });265 });
264 }266 }
265 group.wait(io);267 try group.await(io);
266 }268 }
267 }269 }
268}270}
...@@ -503,7 +505,7 @@ pub fn kdf(...@@ -503,7 +505,7 @@ pub fn kdf(
503 blocks.appendNTimesAssumeCapacity(@splat(0), memory);505 blocks.appendNTimesAssumeCapacity(@splat(0), memory);
504506
505 initBlocks(&blocks, &h0, memory, params.p);507 initBlocks(&blocks, &h0, memory, params.p);
506 processBlocks(&blocks, params.t, memory, params.p, mode, io);508 try processBlocks(&blocks, params.t, memory, params.p, mode, io);
507 finalize(&blocks, memory, params.p, derived_key);509 finalize(&blocks, memory, params.p, derived_key);
508}510}
509511
lib/std/crypto/blake3.zig+10-6
...@@ -1,9 +1,11 @@...@@ -1,9 +1,11 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std");
3const fmt = std.fmt;4const fmt = std.fmt;
4const mem = std.mem;5const mem = std.mem;
5const Io = std.Io;6const Io = std.Io;
6const Thread = std.Thread;7const Thread = std.Thread;
8const Allocator = std.mem.Allocator;
79
8const Vec4 = @Vector(4, u32);10const Vec4 = @Vector(4, u32);
9const Vec8 = @Vector(8, u32);11const Vec8 = @Vector(8, u32);
...@@ -767,7 +769,7 @@ fn buildMerkleTreeLayerParallel(...@@ -767,7 +769,7 @@ fn buildMerkleTreeLayerParallel(
767 key: [8]u32,769 key: [8]u32,
768 flags: Flags,770 flags: Flags,
769 io: Io,771 io: Io,
770) void {772) Io.Cancelable!void {
771 const num_parents = input_cvs.len / 2;773 const num_parents = input_cvs.len / 2;
772774
773 // Process sequentially with SIMD for smaller tree layers to avoid thread overhead775 // Process sequentially with SIMD for smaller tree layers to avoid thread overhead
...@@ -787,6 +789,7 @@ fn buildMerkleTreeLayerParallel(...@@ -787,6 +789,7 @@ fn buildMerkleTreeLayerParallel(
787 const num_workers = Thread.getCpuCount() catch 1;789 const num_workers = Thread.getCpuCount() catch 1;
788 const parents_per_worker = (num_parents + num_workers - 1) / num_workers;790 const parents_per_worker = (num_parents + num_workers - 1) / num_workers;
789 var group: Io.Group = .init;791 var group: Io.Group = .init;
792 defer group.cancel(io);
790793
791 for (0..num_workers) |worker_id| {794 for (0..num_workers) |worker_id| {
792 const start_idx = worker_id * parents_per_worker;795 const start_idx = worker_id * parents_per_worker;
...@@ -801,7 +804,7 @@ fn buildMerkleTreeLayerParallel(...@@ -801,7 +804,7 @@ fn buildMerkleTreeLayerParallel(
801 .flags = flags,804 .flags = flags,
802 }});805 }});
803 }806 }
804 group.wait(io);807 try group.await(io);
805}808}
806809
807fn parentOutput(parent_block: []const u8, key: [8]u32, flags: Flags) Output {810fn parentOutput(parent_block: []const u8, key: [8]u32, flags: Flags) Output {
...@@ -987,7 +990,7 @@ pub const Blake3 = struct {...@@ -987,7 +990,7 @@ pub const Blake3 = struct {
987 d.final(out);990 d.final(out);
988 }991 }
989992
990 pub fn hashParallel(b: []const u8, out: []u8, options: Options, allocator: std.mem.Allocator, io: Io) !void {993 pub fn hashParallel(b: []const u8, out: []u8, options: Options, allocator: Allocator, io: Io) error{ OutOfMemory, Canceled }!void {
991 if (b.len < parallel_threshold) {994 if (b.len < parallel_threshold) {
992 return hash(b, out, options);995 return hash(b, out, options);
993 }996 }
...@@ -1008,6 +1011,7 @@ pub const Blake3 = struct {...@@ -1008,6 +1011,7 @@ pub const Blake3 = struct {
1008 const num_workers = thread_count;1011 const num_workers = thread_count;
1009 const chunks_per_worker = (num_full_chunks + num_workers - 1) / num_workers;1012 const chunks_per_worker = (num_full_chunks + num_workers - 1) / num_workers;
1010 var group: Io.Group = .init;1013 var group: Io.Group = .init;
1014 defer group.cancel(io);
10111015
1012 for (0..num_workers) |worker_id| {1016 for (0..num_workers) |worker_id| {
1013 const start_chunk = worker_id * chunks_per_worker;1017 const start_chunk = worker_id * chunks_per_worker;
...@@ -1022,7 +1026,7 @@ pub const Blake3 = struct {...@@ -1022,7 +1026,7 @@ pub const Blake3 = struct {
1022 .flags = flags,1026 .flags = flags,
1023 }});1027 }});
1024 }1028 }
1025 group.wait(io);1029 try group.await(io);
10261030
1027 // Build Merkle tree in parallel layers using ping-pong buffers1031 // Build Merkle tree in parallel layers using ping-pong buffers
1028 const max_intermediate_size = (num_full_chunks + 1) / 2;1032 const max_intermediate_size = (num_full_chunks + 1) / 2;
...@@ -1040,7 +1044,7 @@ pub const Blake3 = struct {...@@ -1040,7 +1044,7 @@ pub const Blake3 = struct {
1040 const has_odd = current_level.len % 2 == 1;1044 const has_odd = current_level.len % 2 == 1;
1041 const next_level_size = num_parents + @intFromBool(has_odd);1045 const next_level_size = num_parents + @intFromBool(has_odd);
10421046
1043 buildMerkleTreeLayerParallel(1047 try buildMerkleTreeLayerParallel(
1044 current_level[0 .. num_parents * 2],1048 current_level[0 .. num_parents * 2],
1045 next_level_buf[0..num_parents],1049 next_level_buf[0..num_parents],
1046 key_words,1050 key_words,
lib/std/crypto/kangarootwelve.zig+9-7
...@@ -1,9 +1,10 @@...@@ -1,9 +1,10 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std");
3const crypto = std.crypto;4const crypto = std.crypto;
4const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
5const Io = std.Io;6const Io = std.Io;
6const Thread = std.Thread;7const assert = std.debug.assert;
78
8const TurboSHAKE128State = crypto.hash.sha3.TurboShake128(0x06);9const TurboSHAKE128State = crypto.hash.sha3.TurboShake128(0x06);
9const TurboSHAKE256State = crypto.hash.sha3.TurboShake256(0x06);10const TurboSHAKE256State = crypto.hash.sha3.TurboShake256(0x06);
...@@ -598,7 +599,7 @@ inline fn processNLeaves(...@@ -598,7 +599,7 @@ inline fn processNLeaves(
598 output: []align(@alignOf(u64)) u8,599 output: []align(@alignOf(u64)) u8,
599) void {600) void {
600 const cv_size = Variant.cv_size;601 const cv_size = Variant.cv_size;
601 comptime std.debug.assert(cv_size % @sizeOf(u64) == 0);602 comptime assert(cv_size % @sizeOf(u64) == 0);
602603
603 if (view.tryGetSlice(j, j + N * chunk_size)) |leaf_data| {604 if (view.tryGetSlice(j, j + N * chunk_size)) |leaf_data| {
604 var leaf_cvs: [N * cv_size]u8 = undefined;605 var leaf_cvs: [N * cv_size]u8 = undefined;
...@@ -645,7 +646,7 @@ fn processLeafBatch(comptime Variant: type, ctx: LeafBatchContext) void {...@@ -645,7 +646,7 @@ fn processLeafBatch(comptime Variant: type, ctx: LeafBatchContext) void {
645 j += chunk_len;646 j += chunk_len;
646 }647 }
647648
648 std.debug.assert(cvs_offset == ctx.output_cvs.len);649 assert(cvs_offset == ctx.output_cvs.len);
649}650}
650651
651/// Helper to process N leaves in SIMD and absorb CVs into state652/// Helper to process N leaves in SIMD and absorb CVs into state
...@@ -841,7 +842,7 @@ fn ktMultiThreaded(...@@ -841,7 +842,7 @@ fn ktMultiThreaded(
841 total_len: usize,842 total_len: usize,
842 output: []u8,843 output: []u8,
843) !void {844) !void {
844 comptime std.debug.assert(bytes_per_batch % (optimal_vector_len * chunk_size) == 0);845 comptime assert(bytes_per_batch % (optimal_vector_len * chunk_size) == 0);
845846
846 const cv_size = Variant.cv_size;847 const cv_size = Variant.cv_size;
847 const StateType = Variant.StateType;848 const StateType = Variant.StateType;
...@@ -883,6 +884,7 @@ fn ktMultiThreaded(...@@ -883,6 +884,7 @@ fn ktMultiThreaded(
883 var pending_cv_lens: [256]usize = .{0} ** 256;884 var pending_cv_lens: [256]usize = .{0} ** 256;
884885
885 var select: Select = .init(io, select_buf);886 var select: Select = .init(io, select_buf);
887 defer select.cancel();
886 var batches_spawned: usize = 0;888 var batches_spawned: usize = 0;
887 var next_to_process: usize = 0;889 var next_to_process: usize = 0;
888890
...@@ -901,7 +903,7 @@ fn ktMultiThreaded(...@@ -901,7 +903,7 @@ fn ktMultiThreaded(
901 batches_spawned += 1;903 batches_spawned += 1;
902 }904 }
903905
904 const result = select.wait() catch unreachable;906 const result = try select.await();
905 const batch = result.batch;907 const batch = result.batch;
906 const slot = batch.batch_idx % max_concurrent;908 const slot = batch.batch_idx % max_concurrent;
907909
...@@ -925,7 +927,7 @@ fn ktMultiThreaded(...@@ -925,7 +927,7 @@ fn ktMultiThreaded(
925 }927 }
926 }928 }
927929
928 select.group.wait(io);930 assert(select.outstanding == 0);
929 }931 }
930932
931 if (has_partial_leaf) {933 if (has_partial_leaf) {
src/Compilation.zig+12-6
...@@ -4698,7 +4698,7 @@ fn performAllTheWork(...@@ -4698,7 +4698,7 @@ fn performAllTheWork(
4698 });4698 });
4699 }4699 }
47004700
4701 astgen_group.wait(io);4701 try astgen_group.await(io);
4702 }4702 }
47034703
4704 if (comp.zcu) |zcu| {4704 if (comp.zcu) |zcu| {
...@@ -4761,7 +4761,7 @@ fn performAllTheWork(...@@ -4761,7 +4761,7 @@ fn performAllTheWork(
4761 // Since we're skipping analysis, there are no ZCU link tasks.4761 // Since we're skipping analysis, there are no ZCU link tasks.
4762 comp.link_queue.finishZcuQueue(comp);4762 comp.link_queue.finishZcuQueue(comp);
4763 // Let other compilation work finish to collect as many errors as possible.4763 // Let other compilation work finish to collect as many errors as possible.
4764 misc_group.wait(io);4764 try misc_group.await(io);
4765 comp.link_queue.wait(io);4765 comp.link_queue.wait(io);
4766 return;4766 return;
4767 }4767 }
...@@ -4850,18 +4850,22 @@ fn performAllTheWork(...@@ -4850,18 +4850,22 @@ fn performAllTheWork(
4850 comp.link_queue.finishZcuQueue(comp);4850 comp.link_queue.finishZcuQueue(comp);
48514851
4852 // Main thread work is all done, now just wait for all async work.4852 // Main thread work is all done, now just wait for all async work.
4853 misc_group.wait(io);4853 try misc_group.await(io);
4854 comp.link_queue.wait(io);4854 comp.link_queue.wait(io);
4855}4855}
48564856
4857fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node) void {4857fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node) void {
4858 const io = comp.io;4858 const io = comp.io;
48594859
4860 // TODO should this function be cancelable?
4861 const prev_cancel_prot = io.swapCancelProtection(.blocked);
4862 defer _ = io.swapCancelProtection(prev_cancel_prot);
4863
4860 var prelink_group: Io.Group = .init;4864 var prelink_group: Io.Group = .init;
4861 defer prelink_group.cancel(io);4865 defer prelink_group.cancel(io);
48624866
4863 comp.queuePrelinkTasks(comp.oneshot_prelink_tasks.items) catch |err| switch (err) {4867 comp.queuePrelinkTasks(comp.oneshot_prelink_tasks.items) catch |err| switch (err) {
4864 error.Canceled => return,4868 error.Canceled => unreachable, // see swapCancelProtection above
4865 };4869 };
4866 comp.oneshot_prelink_tasks.clearRetainingCapacity();4870 comp.oneshot_prelink_tasks.clearRetainingCapacity();
48674871
...@@ -5055,9 +5059,11 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node...@@ -5055,9 +5059,11 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
5055 });5059 });
5056 }5060 }
50575061
5058 prelink_group.wait(io);5062 prelink_group.await(io) catch |err| switch (err) {
5063 error.Canceled => unreachable, // see swapCancelProtection above
5064 };
5059 comp.link_queue.finishPrelinkQueue(comp) catch |err| switch (err) {5065 comp.link_queue.finishPrelinkQueue(comp) catch |err| switch (err) {
5060 error.Canceled => return,5066 error.Canceled => unreachable, // see swapCancelProtection above
5061 };5067 };
5062}5068}
50635069
src/Package/Fetch.zig+10-6
...@@ -146,6 +146,8 @@ pub const JobQueue = struct {...@@ -146,6 +146,8 @@ pub const JobQueue = struct {
146 pub const UnlazySet = std.AutoArrayHashMapUnmanaged(Package.Hash, void);146 pub const UnlazySet = std.AutoArrayHashMapUnmanaged(Package.Hash, void);
147147
148 pub fn deinit(jq: *JobQueue) void {148 pub fn deinit(jq: *JobQueue) void {
149 const io = jq.io;
150 jq.group.cancel(io);
149 if (jq.all_fetches.items.len == 0) return;151 if (jq.all_fetches.items.len == 0) return;
150 const gpa = jq.all_fetches.items[0].arena.child_allocator;152 const gpa = jq.all_fetches.items[0].arena.child_allocator;
151 jq.table.deinit(gpa);153 jq.table.deinit(gpa);
...@@ -847,7 +849,7 @@ pub fn workerRun(f: *Fetch, prog_name: []const u8) void {...@@ -847,7 +849,7 @@ pub fn workerRun(f: *Fetch, prog_name: []const u8) void {
847849
848 run(f) catch |err| switch (err) {850 run(f) catch |err| switch (err) {
849 error.OutOfMemory => f.oom_flag = true,851 error.OutOfMemory => f.oom_flag = true,
850 error.Canceled => {},852 error.Canceled => {}, // TODO make groupAsync functions be cancelable and assert proper value was returned
851 error.FetchFailed => {853 error.FetchFailed => {
852 // Nothing to do because the errors are already reported in `error_bundle`,854 // Nothing to do because the errors are already reported in `error_bundle`,
853 // and a reference is kept to the `Fetch` task inside `all_fetches`.855 // and a reference is kept to the `Fetch` task inside `all_fetches`.
...@@ -1517,12 +1519,12 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1517,12 +1519,12 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1517 // The final hash will be a hash of each file hashed independently. This1519 // The final hash will be a hash of each file hashed independently. This
1518 // allows hashing in parallel.1520 // allows hashing in parallel.
1519 var group: Io.Group = .init;1521 var group: Io.Group = .init;
1520 defer group.wait(io);1522 defer group.cancel(io);
15211523
1522 while (walker.next(io) catch |err| {1524 while (walker.next(io) catch |err| {
1523 try eb.addRootErrorMessage(.{ .msg = try eb.printString(1525 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1524 "unable to walk temporary directory '{f}': {s}",1526 "unable to walk temporary directory '{f}': {t}",
1525 .{ pkg_path, @errorName(err) },1527 .{ pkg_path, err },
1526 ) });1528 ) });
1527 return error.FetchFailed;1529 return error.FetchFailed;
1528 }) |entry| {1530 }) |entry| {
...@@ -1552,8 +1554,8 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1552,8 +1554,8 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1552 .file => .file,1554 .file => .file,
1553 .sym_link => .link,1555 .sym_link => .link,
1554 else => return f.fail(f.location_tok, try eb.printString(1556 else => return f.fail(f.location_tok, try eb.printString(
1555 "package contains '{s}' which has illegal file type '{s}'",1557 "package contains '{s}' which has illegal file type '{t}'",
1556 .{ entry.path, @tagName(entry.kind) },1558 .{ entry.path, entry.kind },
1557 )),1559 )),
1558 };1560 };
15591561
...@@ -1573,6 +1575,8 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1573,6 +1575,8 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1573 group.async(io, workerHashFile, .{ io, root_dir, hashed_file });1575 group.async(io, workerHashFile, .{ io, root_dir, hashed_file });
1574 try all_files.append(hashed_file);1576 try all_files.append(hashed_file);
1575 }1577 }
1578
1579 try group.await(io);
1576 }1580 }
15771581
1578 {1582 {
src/link/MachO/hasher.zig+1-1
...@@ -48,7 +48,7 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -48,7 +48,7 @@ pub fn ParallelHasher(comptime Hasher: type) type {
48 });48 });
49 }49 }
5050
51 group.wait(io);51 try group.await(io);
52 }52 }
53 for (results) |result| _ = try result;53 for (results) |result| _ = try result;
54 }54 }
src/main.zig+1-1
...@@ -5284,7 +5284,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5284,7 +5284,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5284 );5284 );
52855285
5286 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });5286 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
5287 job_queue.group.wait(io);5287 try job_queue.group.await(io);
52885288
5289 try job_queue.consolidateErrors();5289 try job_queue.consolidateErrors();
52905290
tools/update_cpu_features.zig+1-1
...@@ -1951,7 +1951,7 @@ pub fn main() anyerror!void {...@@ -1951,7 +1951,7 @@ pub fn main() anyerror!void {
1951 } });1951 } });
1952 }1952 }
19531953
1954 group.wait(io);1954 try group.await(io);
1955}1955}
19561956
1957const Job = struct {1957const Job = struct {