authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-11-24 07:37:05-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-11-24 14:34:18-08:00
loga242292644a12b8ca0485759ba45c550265da5bd
treee994615836c82f0ff4b7fed8ac0b1c4540ca9149
parent32dc46aae56623bff9b1fc792d49913f9295be7b

build runner: update from std.Thread.Pool to std.Io


6 files changed, 40 insertions(+), 68 deletions(-)

lib/compiler/build_runner.zig+13-37
...@@ -107,7 +107,6 @@ pub fn main() !void {...@@ -107,7 +107,6 @@ pub fn main() !void {
107107
108 var targets = std.array_list.Managed([]const u8).init(arena);108 var targets = std.array_list.Managed([]const u8).init(arena);
109 var debug_log_scopes = std.array_list.Managed([]const u8).init(arena);109 var debug_log_scopes = std.array_list.Managed([]const u8).init(arena);
110 var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
111110
112 var install_prefix: ?[]const u8 = null;111 var install_prefix: ?[]const u8 = null;
113 var dir_list = std.Build.DirList{};112 var dir_list = std.Build.DirList{};
...@@ -413,19 +412,11 @@ pub fn main() !void {...@@ -413,19 +412,11 @@ pub fn main() !void {
413 };412 };
414 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {413 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
415 builder.reference_trace = null;414 builder.reference_trace = null;
416 } else if (mem.startsWith(u8, arg, "-j")) {415 } else if (mem.cutPrefix(u8, arg, "-j")) |text| {
417 const num = arg["-j".len..];416 const n = std.fmt.parseUnsigned(u32, text, 10) catch |err|
418 const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| {417 fatal("unable to parse jobs count '{s}': {t}", .{ text, err });
419 std.debug.print("unable to parse jobs count '{s}': {s}", .{418 if (n < 1) fatal("number of jobs must be at least 1", .{});
420 num, @errorName(err),419 threaded.setAsyncLimit(.limited(n));
421 });
422 process.exit(1);
423 };
424 if (n_jobs < 1) {
425 std.debug.print("number of jobs must be at least 1\n", .{});
426 process.exit(1);
427 }
428 thread_pool_options.n_jobs = n_jobs;
429 } else if (mem.eql(u8, arg, "--")) {420 } else if (mem.eql(u8, arg, "--")) {
430 builder.args = argsRest(args, arg_idx);421 builder.args = argsRest(args, arg_idx);
431 break;422 break;
...@@ -516,7 +507,6 @@ pub fn main() !void {...@@ -516,7 +507,6 @@ pub fn main() !void {
516 .error_style = error_style,507 .error_style = error_style,
517 .multiline_errors = multiline_errors,508 .multiline_errors = multiline_errors,
518 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,509 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
519 .thread_pool = undefined,
520510
521 .ttyconf = ttyconf,511 .ttyconf = ttyconf,
522 };512 };
...@@ -547,16 +537,12 @@ pub fn main() !void {...@@ -547,16 +537,12 @@ pub fn main() !void {
547 break :w try .init();537 break :w try .init();
548 };538 };
549539
550 try run.thread_pool.init(thread_pool_options);
551 defer run.thread_pool.deinit();
552
553 const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});540 const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
554541
555 run.web_server = if (webui_listen) |listen_address| ws: {542 run.web_server = if (webui_listen) |listen_address| ws: {
556 if (builtin.single_threaded) unreachable; // `fatal` above543 if (builtin.single_threaded) unreachable; // `fatal` above
557 break :ws .init(.{544 break :ws .init(.{
558 .gpa = gpa,545 .gpa = gpa,
559 .thread_pool = &run.thread_pool,
560 .ttyconf = ttyconf,546 .ttyconf = ttyconf,
561 .graph = &graph,547 .graph = &graph,
562 .all_steps = run.step_stack.keys(),548 .all_steps = run.step_stack.keys(),
...@@ -675,7 +661,6 @@ const Run = struct {...@@ -675,7 +661,6 @@ const Run = struct {
675 memory_blocked_steps: std.ArrayList(*Step),661 memory_blocked_steps: std.ArrayList(*Step),
676 /// Allocated into `gpa`.662 /// Allocated into `gpa`.
677 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),663 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
678 thread_pool: std.Thread.Pool,
679 /// Similar to the `tty.Config` returned by `std.debug.lockStderrWriter`,664 /// Similar to the `tty.Config` returned by `std.debug.lockStderrWriter`,
680 /// but also respects the '--color' flag.665 /// but also respects the '--color' flag.
681 ttyconf: tty.Config,666 ttyconf: tty.Config,
...@@ -754,14 +739,13 @@ fn runStepNames(...@@ -754,14 +739,13 @@ fn runStepNames(
754 const gpa = run.gpa;739 const gpa = run.gpa;
755 const io = b.graph.io;740 const io = b.graph.io;
756 const step_stack = &run.step_stack;741 const step_stack = &run.step_stack;
757 const thread_pool = &run.thread_pool;
758742
759 {743 {
760 const step_prog = parent_prog_node.start("steps", step_stack.count());744 const step_prog = parent_prog_node.start("steps", step_stack.count());
761 defer step_prog.end();745 defer step_prog.end();
762746
763 var wait_group: std.Thread.WaitGroup = .{};747 var group: Io.Group = .init;
764 defer wait_group.wait();748 defer group.wait(io);
765749
766 // Here we spawn the initial set of tasks with a nice heuristic -750 // Here we spawn the initial set of tasks with a nice heuristic -
767 // dependency order. Each worker when it finishes a step will then751 // dependency order. Each worker when it finishes a step will then
...@@ -771,9 +755,7 @@ fn runStepNames(...@@ -771,9 +755,7 @@ fn runStepNames(
771 const step = steps_slice[steps_slice.len - i - 1];755 const step = steps_slice[steps_slice.len - i - 1];
772 if (step.state == .skipped_oom) continue;756 if (step.state == .skipped_oom) continue;
773757
774 thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{758 group.async(io, workerMakeOneStep, .{ &group, b, step, step_prog, run });
775 &wait_group, b, step, step_prog, run,
776 });
777 }759 }
778 }760 }
779761
...@@ -855,7 +837,6 @@ fn runStepNames(...@@ -855,7 +837,6 @@ fn runStepNames(
855 var f = std.Build.Fuzz.init(837 var f = std.Build.Fuzz.init(
856 gpa,838 gpa,
857 io,839 io,
858 thread_pool,
859 run.ttyconf,840 run.ttyconf,
860 step_stack.keys(),841 step_stack.keys(),
861 parent_prog_node,842 parent_prog_node,
...@@ -1318,14 +1299,12 @@ fn constructGraphAndCheckForDependencyLoop(...@@ -1318,14 +1299,12 @@ fn constructGraphAndCheckForDependencyLoop(
1318}1299}
13191300
1320fn workerMakeOneStep(1301fn workerMakeOneStep(
1321 wg: *std.Thread.WaitGroup,1302 group: *Io.Group,
1322 b: *std.Build,1303 b: *std.Build,
1323 s: *Step,1304 s: *Step,
1324 prog_node: std.Progress.Node,1305 prog_node: std.Progress.Node,
1325 run: *Run,1306 run: *Run,
1326) void {1307) void {
1327 const thread_pool = &run.thread_pool;
1328
1329 // First, check the conditions for running this step. If they are not met,1308 // First, check the conditions for running this step. If they are not met,
1330 // then we return without doing the step, relying on another worker to1309 // then we return without doing the step, relying on another worker to
1331 // queue this step up again when dependencies are met.1310 // queue this step up again when dependencies are met.
...@@ -1381,7 +1360,6 @@ fn workerMakeOneStep(...@@ -1381,7 +1360,6 @@ fn workerMakeOneStep(
13811360
1382 const make_result = s.make(.{1361 const make_result = s.make(.{
1383 .progress_node = sub_prog_node,1362 .progress_node = sub_prog_node,
1384 .thread_pool = thread_pool,
1385 .watch = run.watch,1363 .watch = run.watch,
1386 .web_server = if (run.web_server) |*ws| ws else null,1364 .web_server = if (run.web_server) |*ws| ws else null,
1387 .ttyconf = run.ttyconf,1365 .ttyconf = run.ttyconf,
...@@ -1400,6 +1378,8 @@ fn workerMakeOneStep(...@@ -1400,6 +1378,8 @@ fn workerMakeOneStep(
1400 printErrorMessages(run.gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {};1378 printErrorMessages(run.gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {};
1401 }1379 }
14021380
1381 const io = b.graph.io;
1382
1403 handle_result: {1383 handle_result: {
1404 if (make_result) |_| {1384 if (make_result) |_| {
1405 @atomicStore(Step.State, &s.state, .success, .seq_cst);1385 @atomicStore(Step.State, &s.state, .success, .seq_cst);
...@@ -1419,9 +1399,7 @@ fn workerMakeOneStep(...@@ -1419,9 +1399,7 @@ fn workerMakeOneStep(
14191399
1420 // Successful completion of a step, so we queue up its dependants as well.1400 // Successful completion of a step, so we queue up its dependants as well.
1421 for (s.dependants.items) |dep| {1401 for (s.dependants.items) |dep| {
1422 thread_pool.spawnWg(wg, workerMakeOneStep, .{1402 group.async(io, workerMakeOneStep, .{ group, b, dep, prog_node, run });
1423 wg, b, dep, prog_node, run,
1424 });
1425 }1403 }
1426 }1404 }
14271405
...@@ -1444,9 +1422,7 @@ fn workerMakeOneStep(...@@ -1444,9 +1422,7 @@ fn workerMakeOneStep(
1444 if (dep.max_rss <= remaining) {1422 if (dep.max_rss <= remaining) {
1445 remaining -= dep.max_rss;1423 remaining -= dep.max_rss;
14461424
1447 thread_pool.spawnWg(wg, workerMakeOneStep, .{1425 group.async(io, workerMakeOneStep, .{ group, b, dep, prog_node, run });
1448 wg, b, dep, prog_node, run,
1449 });
1450 } else {1426 } else {
1451 run.memory_blocked_steps.items[i] = dep;1427 run.memory_blocked_steps.items[i] = dep;
1452 i += 1;1428 i += 1;
lib/std/Build/Fuzz.zig+16-22
...@@ -22,10 +22,9 @@ mode: Mode,...@@ -22,10 +22,9 @@ mode: Mode,
22/// Allocated into `gpa`.22/// Allocated into `gpa`.
23run_steps: []const *Step.Run,23run_steps: []const *Step.Run,
2424
25wait_group: std.Thread.WaitGroup,25group: Io.Group,
26root_prog_node: std.Progress.Node,26root_prog_node: std.Progress.Node,
27prog_node: std.Progress.Node,27prog_node: std.Progress.Node,
28thread_pool: *std.Thread.Pool,
2928
30/// Protects `coverage_files`.29/// Protects `coverage_files`.
31coverage_mutex: std.Thread.Mutex,30coverage_mutex: std.Thread.Mutex,
...@@ -78,7 +77,6 @@ const CoverageMap = struct {...@@ -78,7 +77,6 @@ const CoverageMap = struct {
78pub fn init(77pub fn init(
79 gpa: Allocator,78 gpa: Allocator,
80 io: Io,79 io: Io,
81 thread_pool: *std.Thread.Pool,
82 ttyconf: tty.Config,80 ttyconf: tty.Config,
83 all_steps: []const *Build.Step,81 all_steps: []const *Build.Step,
84 root_prog_node: std.Progress.Node,82 root_prog_node: std.Progress.Node,
...@@ -89,20 +87,22 @@ pub fn init(...@@ -89,20 +87,22 @@ pub fn init(
89 defer steps.deinit(gpa);87 defer steps.deinit(gpa);
90 const rebuild_node = root_prog_node.start("Rebuilding Unit Tests", 0);88 const rebuild_node = root_prog_node.start("Rebuilding Unit Tests", 0);
91 defer rebuild_node.end();89 defer rebuild_node.end();
92 var rebuild_wg: std.Thread.WaitGroup = .{};90 var rebuild_group: Io.Group = .init;
93 defer rebuild_wg.wait();91 defer rebuild_group.cancel(io);
9492
95 for (all_steps) |step| {93 for (all_steps) |step| {
96 const run = step.cast(Step.Run) orelse continue;94 const run = step.cast(Step.Run) orelse continue;
97 if (run.producer == null) continue;95 if (run.producer == null) continue;
98 if (run.fuzz_tests.items.len == 0) continue;96 if (run.fuzz_tests.items.len == 0) continue;
99 try steps.append(gpa, run);97 try steps.append(gpa, run);
100 thread_pool.spawnWg(&rebuild_wg, rebuildTestsWorkerRun, .{ run, gpa, ttyconf, rebuild_node });98 rebuild_group.async(io, rebuildTestsWorkerRun, .{ run, gpa, ttyconf, rebuild_node });
101 }99 }
102100
103 if (steps.items.len == 0) fatal("no fuzz tests found", .{});101 if (steps.items.len == 0) fatal("no fuzz tests found", .{});
104 rebuild_node.setEstimatedTotalItems(steps.items.len);102 rebuild_node.setEstimatedTotalItems(steps.items.len);
105 break :steps try gpa.dupe(*Step.Run, steps.items);103 const run_steps = try gpa.dupe(*Step.Run, steps.items);
104 rebuild_group.wait(io);
105 break :steps run_steps;
106 };106 };
107 errdefer gpa.free(run_steps);107 errdefer gpa.free(run_steps);
108108
...@@ -118,8 +118,7 @@ pub fn init(...@@ -118,8 +118,7 @@ pub fn init(
118 .ttyconf = ttyconf,118 .ttyconf = ttyconf,
119 .mode = mode,119 .mode = mode,
120 .run_steps = run_steps,120 .run_steps = run_steps,
121 .wait_group = .{},121 .group = .init,
122 .thread_pool = thread_pool,
123 .root_prog_node = root_prog_node,122 .root_prog_node = root_prog_node,
124 .prog_node = .none,123 .prog_node = .none,
125 .coverage_files = .empty,124 .coverage_files = .empty,
...@@ -131,29 +130,26 @@ pub fn init(...@@ -131,29 +130,26 @@ pub fn init(
131}130}
132131
133pub fn start(fuzz: *Fuzz) void {132pub fn start(fuzz: *Fuzz) void {
133 const io = fuzz.io;
134 fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", fuzz.run_steps.len);134 fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", fuzz.run_steps.len);
135135
136 if (fuzz.mode == .forever) {136 if (fuzz.mode == .forever) {
137 // For polling messages and sending updates to subscribers.137 // For polling messages and sending updates to subscribers.
138 fuzz.wait_group.start();138 fuzz.group.concurrent(io, coverageRun, .{fuzz}) catch |err|
139 _ = std.Thread.spawn(.{}, coverageRun, .{fuzz}) catch |err| {139 fatal("unable to spawn coverage task: {t}", .{err});
140 fuzz.wait_group.finish();
141 fatal("unable to spawn coverage thread: {s}", .{@errorName(err)});
142 };
143 }140 }
144141
145 for (fuzz.run_steps) |run| {142 for (fuzz.run_steps) |run| {
146 for (run.fuzz_tests.items) |unit_test_index| {143 for (run.fuzz_tests.items) |unit_test_index| {
147 assert(run.rebuilt_executable != null);144 assert(run.rebuilt_executable != null);
148 fuzz.thread_pool.spawnWg(&fuzz.wait_group, fuzzWorkerRun, .{145 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run, unit_test_index });
149 fuzz, run, unit_test_index,
150 });
151 }146 }
152 }147 }
153}148}
154149
155pub fn deinit(fuzz: *Fuzz) void {150pub fn deinit(fuzz: *Fuzz) void {
156 if (!fuzz.wait_group.isDone()) @panic("TODO: terminate the fuzzer processes");151 const io = fuzz.io;
152 fuzz.group.cancel(io);
157 fuzz.prog_node.end();153 fuzz.prog_node.end();
158 fuzz.gpa.free(fuzz.run_steps);154 fuzz.gpa.free(fuzz.run_steps);
159}155}
...@@ -335,8 +331,6 @@ pub fn sendUpdate(...@@ -335,8 +331,6 @@ pub fn sendUpdate(
335}331}
336332
337fn coverageRun(fuzz: *Fuzz) void {333fn coverageRun(fuzz: *Fuzz) void {
338 defer fuzz.wait_group.finish();
339
340 fuzz.queue_mutex.lock();334 fuzz.queue_mutex.lock();
341 defer fuzz.queue_mutex.unlock();335 defer fuzz.queue_mutex.unlock();
342336
...@@ -511,8 +505,8 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {...@@ -511,8 +505,8 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {
511 assert(fuzz.mode == .limit);505 assert(fuzz.mode == .limit);
512 const io = fuzz.io;506 const io = fuzz.io;
513507
514 fuzz.wait_group.wait();508 fuzz.group.wait(io);
515 fuzz.wait_group.reset();509 fuzz.group = .init;
516510
517 std.debug.print("======= FUZZING REPORT =======\n", .{});511 std.debug.print("======= FUZZING REPORT =======\n", .{});
518 for (fuzz.msg_queue.items) |msg| {512 for (fuzz.msg_queue.items) |msg| {
lib/std/Build/Step.zig-1
...@@ -110,7 +110,6 @@ pub const TestResults = struct {...@@ -110,7 +110,6 @@ pub const TestResults = struct {
110110
111pub const MakeOptions = struct {111pub const MakeOptions = struct {
112 progress_node: std.Progress.Node,112 progress_node: std.Progress.Node,
113 thread_pool: *std.Thread.Pool,
114 watch: bool,113 watch: bool,
115 web_server: switch (builtin.target.cpu.arch) {114 web_server: switch (builtin.target.cpu.arch) {
116 else => ?*Build.WebServer,115 else => ?*Build.WebServer,
lib/std/Build/Step/Run.zig-1
...@@ -1151,7 +1151,6 @@ pub fn rerunInFuzzMode(...@@ -1151,7 +1151,6 @@ pub fn rerunInFuzzMode(
1151 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);1151 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1152 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{1152 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
1153 .progress_node = prog_node,1153 .progress_node = prog_node,
1154 .thread_pool = undefined, // not used by `runCommand`
1155 .watch = undefined, // not used by `runCommand`1154 .watch = undefined, // not used by `runCommand`
1156 .web_server = null, // only needed for time reports1155 .web_server = null, // only needed for time reports
1157 .ttyconf = fuzz.ttyconf,1156 .ttyconf = fuzz.ttyconf,
lib/std/Build/WebServer.zig-4
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1gpa: Allocator,1gpa: Allocator,
2thread_pool: *std.Thread.Pool,
3graph: *const Build.Graph,2graph: *const Build.Graph,
4all_steps: []const *Build.Step,3all_steps: []const *Build.Step,
5listen_address: net.IpAddress,4listen_address: net.IpAddress,
...@@ -53,7 +52,6 @@ pub fn notifyUpdate(ws: *WebServer) void {...@@ -53,7 +52,6 @@ pub fn notifyUpdate(ws: *WebServer) void {
5352
54pub const Options = struct {53pub const Options = struct {
55 gpa: Allocator,54 gpa: Allocator,
56 thread_pool: *std.Thread.Pool,
57 ttyconf: Io.tty.Config,55 ttyconf: Io.tty.Config,
58 graph: *const std.Build.Graph,56 graph: *const std.Build.Graph,
59 all_steps: []const *Build.Step,57 all_steps: []const *Build.Step,
...@@ -100,7 +98,6 @@ pub fn init(opts: Options) WebServer {...@@ -100,7 +98,6 @@ pub fn init(opts: Options) WebServer {
10098
101 return .{99 return .{
102 .gpa = opts.gpa,100 .gpa = opts.gpa,
103 .thread_pool = opts.thread_pool,
104 .ttyconf = opts.ttyconf,101 .ttyconf = opts.ttyconf,
105 .graph = opts.graph,102 .graph = opts.graph,
106 .all_steps = all_steps,103 .all_steps = all_steps,
...@@ -235,7 +232,6 @@ pub fn finishBuild(ws: *WebServer, opts: struct {...@@ -235,7 +232,6 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
235 ws.fuzz = Fuzz.init(232 ws.fuzz = Fuzz.init(
236 ws.gpa,233 ws.gpa,
237 ws.graph.io,234 ws.graph.io,
238 ws.thread_pool,
239 ws.ttyconf,235 ws.ttyconf,
240 ws.all_steps,236 ws.all_steps,
241 ws.root_prog_node,237 ws.root_prog_node,
lib/std/Io/Threaded.zig+11-3
...@@ -33,6 +33,9 @@ wait_group: std.Thread.WaitGroup = .{},...@@ -33,6 +33,9 @@ wait_group: std.Thread.WaitGroup = .{},
33/// immediately.33/// immediately.
34///34///
35/// Defaults to a number equal to logical CPU cores.35/// Defaults to a number equal to logical CPU cores.
36///
37/// Protected by `mutex` once the I/O instance is already in use. See
38/// `setAsyncLimit`.
36async_limit: Io.Limit,39async_limit: Io.Limit,
37/// Maximum thread pool size (excluding main thread) for dispatching concurrent40/// Maximum thread pool size (excluding main thread) for dispatching concurrent
38/// tasks. Until this limit, calls to `Io.concurrent` will increase the thread41/// tasks. Until this limit, calls to `Io.concurrent` will increase the thread
...@@ -168,6 +171,12 @@ pub const init_single_threaded: Threaded = .{...@@ -168,6 +171,12 @@ pub const init_single_threaded: Threaded = .{
168 .have_signal_handler = false,171 .have_signal_handler = false,
169};172};
170173
174pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
175 t.mutex.lock();
176 defer t.mutex.unlock();
177 t.async_limit = new_limit;
178}
179
171pub fn deinit(t: *Threaded) void {180pub fn deinit(t: *Threaded) void {
172 t.join();181 t.join();
173 if (is_windows and t.wsa.status == .initialized) {182 if (is_windows and t.wsa.status == .initialized) {
...@@ -507,7 +516,7 @@ fn async(...@@ -507,7 +516,7 @@ fn async(
507 start: *const fn (context: *const anyopaque, result: *anyopaque) void,516 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
508) ?*Io.AnyFuture {517) ?*Io.AnyFuture {
509 const t: *Threaded = @ptrCast(@alignCast(userdata));518 const t: *Threaded = @ptrCast(@alignCast(userdata));
510 if (builtin.single_threaded or t.async_limit == .nothing) {519 if (builtin.single_threaded) {
511 start(context.ptr, result.ptr);520 start(context.ptr, result.ptr);
512 return null;521 return null;
513 }522 }
...@@ -684,8 +693,7 @@ fn groupAsync(...@@ -684,8 +693,7 @@ fn groupAsync(
684 start: *const fn (*Io.Group, context: *const anyopaque) void,693 start: *const fn (*Io.Group, context: *const anyopaque) void,
685) void {694) void {
686 const t: *Threaded = @ptrCast(@alignCast(userdata));695 const t: *Threaded = @ptrCast(@alignCast(userdata));
687 if (builtin.single_threaded or t.async_limit == .nothing)696 if (builtin.single_threaded) return start(group, context.ptr);
688 return start(group, context.ptr);
689697
690 const gpa = t.allocator;698 const gpa = t.allocator;
691 const gc = GroupClosure.init(gpa, t, group, context, context_alignment, start) catch699 const gc = GroupClosure.init(gpa, t, group, context, context_alignment, start) catch