authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-03 17:57:48-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-03 20:58:02-07:00
loga72292513e378159c542a785ca69f8111bacbdcf
treeaac9e20a88f90e9117b338bcc0e925c7ffa90182
parenta96b78c170ef0464e51a1c2fa226c51d49cfde04

add std.Thread.Pool.spawnWg

This function accepts a WaitGroup parameter and manages the reference counting therein. It also is infallible. The existing `spawn` function is still handy when the job wants to further schedule more tasks.

6 files changed, 88 insertions(+), 86 deletions(-)

lib/compiler/build_runner.zig+6-11
...@@ -466,10 +466,9 @@ fn runStepNames(...@@ -466,10 +466,9 @@ fn runStepNames(
466 const step = steps_slice[steps_slice.len - i - 1];466 const step = steps_slice[steps_slice.len - i - 1];
467 if (step.state == .skipped_oom) continue;467 if (step.state == .skipped_oom) continue;
468468
469 wait_group.start();469 thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{
470 thread_pool.spawn(workerMakeOneStep, .{
471 &wait_group, &thread_pool, b, step, &step_prog, run,470 &wait_group, &thread_pool, b, step, &step_prog, run,
472 }) catch @panic("OOM");471 });
473 }472 }
474 }473 }
475 assert(run.memory_blocked_steps.items.len == 0);474 assert(run.memory_blocked_steps.items.len == 0);
...@@ -895,8 +894,6 @@ fn workerMakeOneStep(...@@ -895,8 +894,6 @@ fn workerMakeOneStep(
895 prog_node: *std.Progress.Node,894 prog_node: *std.Progress.Node,
896 run: *Run,895 run: *Run,
897) void {896) void {
898 defer wg.finish();
899
900 // First, check the conditions for running this step. If they are not met,897 // First, check the conditions for running this step. If they are not met,
901 // then we return without doing the step, relying on another worker to898 // then we return without doing the step, relying on another worker to
902 // queue this step up again when dependencies are met.899 // queue this step up again when dependencies are met.
...@@ -976,10 +973,9 @@ fn workerMakeOneStep(...@@ -976,10 +973,9 @@ fn workerMakeOneStep(
976973
977 // Successful completion of a step, so we queue up its dependants as well.974 // Successful completion of a step, so we queue up its dependants as well.
978 for (s.dependants.items) |dep| {975 for (s.dependants.items) |dep| {
979 wg.start();976 thread_pool.spawnWg(wg, workerMakeOneStep, .{
980 thread_pool.spawn(workerMakeOneStep, .{
981 wg, thread_pool, b, dep, prog_node, run,977 wg, thread_pool, b, dep, prog_node, run,
982 }) catch @panic("OOM");978 });
983 }979 }
984 }980 }
985981
...@@ -1002,10 +998,9 @@ fn workerMakeOneStep(...@@ -1002,10 +998,9 @@ fn workerMakeOneStep(
1002 if (dep.max_rss <= remaining) {998 if (dep.max_rss <= remaining) {
1003 remaining -= dep.max_rss;999 remaining -= dep.max_rss;
10041000
1005 wg.start();1001 thread_pool.spawnWg(wg, workerMakeOneStep, .{
1006 thread_pool.spawn(workerMakeOneStep, .{
1007 wg, thread_pool, b, dep, prog_node, run,1002 wg, thread_pool, b, dep, prog_node, run,
1008 }) catch @panic("OOM");1003 });
1009 } else {1004 } else {
1010 run.memory_blocked_steps.items[i] = dep;1005 run.memory_blocked_steps.items[i] = dep;
1011 i += 1;1006 i += 1;
lib/std/Thread/Pool.zig+59
...@@ -75,6 +75,65 @@ fn join(pool: *Pool, spawned: usize) void {...@@ -75,6 +75,65 @@ fn join(pool: *Pool, spawned: usize) void {
75 pool.allocator.free(pool.threads);75 pool.allocator.free(pool.threads);
76}76}
7777
78/// Runs `func` in the thread pool, calling `WaitGroup.start` beforehand, and
79/// `WaitGroup.finish` after it returns.
80///
81/// In the case that queuing the function call fails to allocate memory, or the
82/// target is single-threaded, the function is called directly.
83pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args: anytype) void {
84 wait_group.start();
85
86 if (builtin.single_threaded) {
87 @call(.auto, func, args);
88 wait_group.finish();
89 return;
90 }
91
92 const Args = @TypeOf(args);
93 const Closure = struct {
94 arguments: Args,
95 pool: *Pool,
96 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
97 wait_group: *WaitGroup,
98
99 fn runFn(runnable: *Runnable) void {
100 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
101 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
102 @call(.auto, func, closure.arguments);
103 closure.wait_group.finish();
104
105 // The thread pool's allocator is protected by the mutex.
106 const mutex = &closure.pool.mutex;
107 mutex.lock();
108 defer mutex.unlock();
109
110 closure.pool.allocator.destroy(closure);
111 }
112 };
113
114 {
115 pool.mutex.lock();
116
117 const closure = pool.allocator.create(Closure) catch {
118 pool.mutex.unlock();
119 @call(.auto, func, args);
120 wait_group.finish();
121 return;
122 };
123 closure.* = .{
124 .arguments = args,
125 .pool = pool,
126 .wait_group = wait_group,
127 };
128
129 pool.run_queue.prepend(&closure.run_node);
130 pool.mutex.unlock();
131 }
132
133 // Notify waiting threads outside the lock to try and keep the critical section small.
134 pool.cond.signal();
135}
136
78pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {137pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
79 if (builtin.single_threaded) {138 if (builtin.single_threaded) {
80 @call(.auto, func, args);139 @call(.auto, func, args);
src/Compilation.zig+14-46
...@@ -3273,7 +3273,7 @@ pub fn performAllTheWork(...@@ -3273,7 +3273,7 @@ pub fn performAllTheWork(
32733273
3274 if (!build_options.only_c and !build_options.only_core_functionality) {3274 if (!build_options.only_c and !build_options.only_core_functionality) {
3275 if (comp.docs_emit != null) {3275 if (comp.docs_emit != null) {
3276 try taskDocsCopy(comp, &comp.work_queue_wait_group);3276 comp.thread_pool.spawnWg(&comp.work_queue_wait_group, workerDocsCopy, .{comp});
3277 comp.work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, &wasm_prog_node });3277 comp.work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, &wasm_prog_node });
3278 }3278 }
3279 }3279 }
...@@ -3305,39 +3305,34 @@ pub fn performAllTheWork(...@@ -3305,39 +3305,34 @@ pub fn performAllTheWork(
33053305
3306 const file = mod.builtin_file orelse continue;3306 const file = mod.builtin_file orelse continue;
33073307
3308 comp.astgen_wait_group.start();3308 comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerUpdateBuiltinZigFile, .{
3309 try comp.thread_pool.spawn(workerUpdateBuiltinZigFile, .{3309 comp, mod, file,
3310 comp, mod, file, &comp.astgen_wait_group,
3311 });3310 });
3312 }3311 }
3313 }3312 }
33143313
3315 while (comp.astgen_work_queue.readItem()) |file| {3314 while (comp.astgen_work_queue.readItem()) |file| {
3316 comp.astgen_wait_group.start();3315 comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerAstGenFile, .{
3317 try comp.thread_pool.spawn(workerAstGenFile, .{
3318 comp, file, &zir_prog_node, &comp.astgen_wait_group, .root,3316 comp, file, &zir_prog_node, &comp.astgen_wait_group, .root,
3319 });3317 });
3320 }3318 }
33213319
3322 while (comp.embed_file_work_queue.readItem()) |embed_file| {3320 while (comp.embed_file_work_queue.readItem()) |embed_file| {
3323 comp.astgen_wait_group.start();3321 comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerCheckEmbedFile, .{
3324 try comp.thread_pool.spawn(workerCheckEmbedFile, .{3322 comp, embed_file,
3325 comp, embed_file, &comp.astgen_wait_group,
3326 });3323 });
3327 }3324 }
33283325
3329 while (comp.c_object_work_queue.readItem()) |c_object| {3326 while (comp.c_object_work_queue.readItem()) |c_object| {
3330 comp.work_queue_wait_group.start();3327 comp.thread_pool.spawnWg(&comp.work_queue_wait_group, workerUpdateCObject, .{
3331 try comp.thread_pool.spawn(workerUpdateCObject, .{3328 comp, c_object, &c_obj_prog_node,
3332 comp, c_object, &c_obj_prog_node, &comp.work_queue_wait_group,
3333 });3329 });
3334 }3330 }
33353331
3336 if (!build_options.only_core_functionality) {3332 if (!build_options.only_core_functionality) {
3337 while (comp.win32_resource_work_queue.readItem()) |win32_resource| {3333 while (comp.win32_resource_work_queue.readItem()) |win32_resource| {
3338 comp.work_queue_wait_group.start();3334 comp.thread_pool.spawnWg(&comp.work_queue_wait_group, workerUpdateWin32Resource, .{
3339 try comp.thread_pool.spawn(workerUpdateWin32Resource, .{3335 comp, win32_resource, &win32_resource_prog_node,
3340 comp, win32_resource, &win32_resource_prog_node, &comp.work_queue_wait_group,
3341 });3336 });
3342 }3337 }
3343 }3338 }
...@@ -3680,14 +3675,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v...@@ -3680,14 +3675,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v
3680 }3675 }
3681}3676}
36823677
3683fn taskDocsCopy(comp: *Compilation, wg: *WaitGroup) !void {3678fn workerDocsCopy(comp: *Compilation) void {
3684 wg.start();
3685 errdefer wg.finish();
3686 try comp.thread_pool.spawn(workerDocsCopy, .{ comp, wg });
3687}
3688
3689fn workerDocsCopy(comp: *Compilation, wg: *WaitGroup) void {
3690 defer wg.finish();
3691 docsCopyFallible(comp) catch |err| {3679 docsCopyFallible(comp) catch |err| {
3692 return comp.lockAndSetMiscFailure(3680 return comp.lockAndSetMiscFailure(
3693 .docs_copy,3681 .docs_copy,
...@@ -3965,8 +3953,6 @@ fn workerAstGenFile(...@@ -3965,8 +3953,6 @@ fn workerAstGenFile(
3965 wg: *WaitGroup,3953 wg: *WaitGroup,
3966 src: AstGenSrc,3954 src: AstGenSrc,
3967) void {3955) void {
3968 defer wg.finish();
3969
3970 var child_prog_node = prog_node.start(file.sub_file_path, 0);3956 var child_prog_node = prog_node.start(file.sub_file_path, 0);
3971 child_prog_node.activate();3957 child_prog_node.activate();
3972 defer child_prog_node.end();3958 defer child_prog_node.end();
...@@ -4025,13 +4011,9 @@ fn workerAstGenFile(...@@ -4025,13 +4011,9 @@ fn workerAstGenFile(
4025 .importing_file = file,4011 .importing_file = file,
4026 .import_tok = item.data.token,4012 .import_tok = item.data.token,
4027 } };4013 } };
4028 wg.start();4014 comp.thread_pool.spawnWg(wg, workerAstGenFile, .{
4029 comp.thread_pool.spawn(workerAstGenFile, .{
4030 comp, import_result.file, prog_node, wg, sub_src,4015 comp, import_result.file, prog_node, wg, sub_src,
4031 }) catch {4016 });
4032 wg.finish();
4033 continue;
4034 };
4035 }4017 }
4036 }4018 }
4037 }4019 }
...@@ -4041,9 +4023,7 @@ fn workerUpdateBuiltinZigFile(...@@ -4041,9 +4023,7 @@ fn workerUpdateBuiltinZigFile(
4041 comp: *Compilation,4023 comp: *Compilation,
4042 mod: *Package.Module,4024 mod: *Package.Module,
4043 file: *Module.File,4025 file: *Module.File,
4044 wg: *WaitGroup,
4045) void {4026) void {
4046 defer wg.finish();
4047 Builtin.populateFile(comp, mod, file) catch |err| {4027 Builtin.populateFile(comp, mod, file) catch |err| {
4048 comp.mutex.lock();4028 comp.mutex.lock();
4049 defer comp.mutex.unlock();4029 defer comp.mutex.unlock();
...@@ -4054,13 +4034,7 @@ fn workerUpdateBuiltinZigFile(...@@ -4054,13 +4034,7 @@ fn workerUpdateBuiltinZigFile(
4054 };4034 };
4055}4035}
40564036
4057fn workerCheckEmbedFile(4037fn workerCheckEmbedFile(comp: *Compilation, embed_file: *Module.EmbedFile) void {
4058 comp: *Compilation,
4059 embed_file: *Module.EmbedFile,
4060 wg: *WaitGroup,
4061) void {
4062 defer wg.finish();
4063
4064 comp.detectEmbedFileUpdate(embed_file) catch |err| {4038 comp.detectEmbedFileUpdate(embed_file) catch |err| {
4065 comp.reportRetryableEmbedFileError(embed_file, err) catch |oom| switch (oom) {4039 comp.reportRetryableEmbedFileError(embed_file, err) catch |oom| switch (oom) {
4066 // Swallowing this error is OK because it's implied to be OOM when4040 // Swallowing this error is OK because it's implied to be OOM when
...@@ -4289,10 +4263,7 @@ fn workerUpdateCObject(...@@ -4289,10 +4263,7 @@ fn workerUpdateCObject(
4289 comp: *Compilation,4263 comp: *Compilation,
4290 c_object: *CObject,4264 c_object: *CObject,
4291 progress_node: *std.Progress.Node,4265 progress_node: *std.Progress.Node,
4292 wg: *WaitGroup,
4293) void {4266) void {
4294 defer wg.finish();
4295
4296 comp.updateCObject(c_object, progress_node) catch |err| switch (err) {4267 comp.updateCObject(c_object, progress_node) catch |err| switch (err) {
4297 error.AnalysisFail => return,4268 error.AnalysisFail => return,
4298 else => {4269 else => {
...@@ -4309,10 +4280,7 @@ fn workerUpdateWin32Resource(...@@ -4309,10 +4280,7 @@ fn workerUpdateWin32Resource(
4309 comp: *Compilation,4280 comp: *Compilation,
4310 win32_resource: *Win32Resource,4281 win32_resource: *Win32Resource,
4311 progress_node: *std.Progress.Node,4282 progress_node: *std.Progress.Node,
4312 wg: *WaitGroup,
4313) void {4283) void {
4314 defer wg.finish();
4315
4316 comp.updateWin32Resource(win32_resource, progress_node) catch |err| switch (err) {4284 comp.updateWin32Resource(win32_resource, progress_node) catch |err| switch (err) {
4317 error.AnalysisFail => return,4285 error.AnalysisFail => return,
4318 else => {4286 else => {
src/Package/Fetch.zig+5-22
...@@ -722,14 +722,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {...@@ -722,14 +722,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
722 const thread_pool = f.job_queue.thread_pool;722 const thread_pool = f.job_queue.thread_pool;
723723
724 for (new_fetches, prog_names) |*new_fetch, prog_name| {724 for (new_fetches, prog_names) |*new_fetch, prog_name| {
725 f.job_queue.wait_group.start();725 thread_pool.spawnWg(&f.job_queue.wait_group, workerRun, .{ new_fetch, prog_name });
726 thread_pool.spawn(workerRun, .{ new_fetch, prog_name }) catch |err| switch (err) {
727 error.OutOfMemory => {
728 new_fetch.oom_flag = true;
729 f.job_queue.wait_group.finish();
730 continue;
731 },
732 };
733 }726 }
734}727}
735728
...@@ -750,8 +743,6 @@ pub fn relativePathDigest(...@@ -750,8 +743,6 @@ pub fn relativePathDigest(
750}743}
751744
752pub fn workerRun(f: *Fetch, prog_name: []const u8) void {745pub fn workerRun(f: *Fetch, prog_name: []const u8) void {
753 defer f.job_queue.wait_group.finish();
754
755 var prog_node = f.prog_node.start(prog_name, 0);746 var prog_node = f.prog_node.start(prog_name, 0);
756 defer prog_node.end();747 defer prog_node.end();
757 prog_node.activate();748 prog_node.activate();
...@@ -1477,10 +1468,7 @@ fn computeHash(...@@ -1477,10 +1468,7 @@ fn computeHash(
1477 .fs_path = fs_path,1468 .fs_path = fs_path,
1478 .failure = undefined, // to be populated by the worker1469 .failure = undefined, // to be populated by the worker
1479 };1470 };
1480 wait_group.start();1471 thread_pool.spawnWg(&wait_group, workerDeleteFile, .{ root_dir, deleted_file });
1481 try thread_pool.spawn(workerDeleteFile, .{
1482 root_dir, deleted_file, &wait_group,
1483 });
1484 try deleted_files.append(deleted_file);1472 try deleted_files.append(deleted_file);
1485 continue;1473 continue;
1486 }1474 }
...@@ -1507,10 +1495,7 @@ fn computeHash(...@@ -1507,10 +1495,7 @@ fn computeHash(
1507 .hash = undefined, // to be populated by the worker1495 .hash = undefined, // to be populated by the worker
1508 .failure = undefined, // to be populated by the worker1496 .failure = undefined, // to be populated by the worker
1509 };1497 };
1510 wait_group.start();1498 thread_pool.spawnWg(&wait_group, workerHashFile, .{ root_dir, hashed_file });
1511 try thread_pool.spawn(workerHashFile, .{
1512 root_dir, hashed_file, &wait_group,
1513 });
1514 try all_files.append(hashed_file);1499 try all_files.append(hashed_file);
1515 }1500 }
1516 }1501 }
...@@ -1602,13 +1587,11 @@ fn dumpHashInfo(all_files: []const *const HashedFile) !void {...@@ -1602,13 +1587,11 @@ fn dumpHashInfo(all_files: []const *const HashedFile) !void {
1602 try bw.flush();1587 try bw.flush();
1603}1588}
16041589
1605fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void {1590fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile) void {
1606 defer wg.finish();
1607 hashed_file.failure = hashFileFallible(dir, hashed_file);1591 hashed_file.failure = hashFileFallible(dir, hashed_file);
1608}1592}
16091593
1610fn workerDeleteFile(dir: fs.Dir, deleted_file: *DeletedFile, wg: *WaitGroup) void {1594fn workerDeleteFile(dir: fs.Dir, deleted_file: *DeletedFile) void {
1611 defer wg.finish();
1612 deleted_file.failure = deleteFileFallible(dir, deleted_file);1595 deleted_file.failure = deleteFileFallible(dir, deleted_file);
1613}1596}
16141597
src/link/MachO/hasher.zig+1-5
...@@ -36,14 +36,12 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -36,14 +36,12 @@ pub fn ParallelHasher(comptime Hasher: type) type {
36 file_size - fstart36 file_size - fstart
37 else37 else
38 chunk_size;38 chunk_size;
39 wg.start();39 self.thread_pool.spawnWg(&wg, worker, .{
40 try self.thread_pool.spawn(worker, .{
41 file,40 file,
42 fstart,41 fstart,
43 buffer[fstart..][0..fsize],42 buffer[fstart..][0..fsize],
44 &(out_buf.*),43 &(out_buf.*),
45 &(result.*),44 &(result.*),
46 &wg,
47 });45 });
48 }46 }
49 }47 }
...@@ -56,9 +54,7 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -56,9 +54,7 @@ pub fn ParallelHasher(comptime Hasher: type) type {
56 buffer: []u8,54 buffer: []u8,
57 out: *[hash_size]u8,55 out: *[hash_size]u8,
58 err: *fs.File.PReadError!usize,56 err: *fs.File.PReadError!usize,
59 wg: *WaitGroup,
60 ) void {57 ) void {
61 defer wg.finish();
62 err.* = file.preadAll(buffer, fstart);58 err.* = file.preadAll(buffer, fstart);
63 Hasher.hash(buffer, out, .{});59 Hasher.hash(buffer, out, .{});
64 }60 }
src/main.zig+3-2
...@@ -5109,8 +5109,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5109,8 +5109,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5109 &fetch,5109 &fetch,
5110 );5110 );
51115111
5112 job_queue.wait_group.start();5112 job_queue.thread_pool.spawnWg(&job_queue.wait_group, Package.Fetch.workerRun, .{
5113 try job_queue.thread_pool.spawn(Package.Fetch.workerRun, .{ &fetch, "root" });5113 &fetch, "root",
5114 });
5114 job_queue.wait_group.wait();5115 job_queue.wait_group.wait();
51155116
5116 try job_queue.consolidateErrors();5117 try job_queue.consolidateErrors();