authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-06-20 19:44:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-06-30 13:54:02-07:00
loge51fd6728fcc4dd93e3bd1ddb2e4ca96a8082b12
tree07b916bb71e2f6f733575bb3396a52c2a58a6af0
parentc395df25aba0f1bdc1dd0cb6b9c7f14a90e72dae

new thread pool jobserver integration

std.Thread.Pool: back to spawning all threads in initialization because it's overall simpler. This scheme requires init to be passed a pointer to the struct. std.process.Child: implement integration with thread pool jobserver. The environment variable is called `JOBSERVERV2`. The API works based on assigning a thread pool to the child process. build runner: store the thread pool in std.Build.Graph so that it can be passed to child processes during the make phase. Fix not allocating +1 pollfds in previous commit.

11 files changed, 171 insertions(+), 85 deletions(-)

lib/compiler/build_runner.zig+9-5
......@@ -74,6 +74,7 @@ pub fn main() !void {
7474 .query = .{},
7575 .result = try std.zig.system.resolveTargetQuery(.{}),
7676 },
77 .thread_pool = undefined,
7778 };
7879
7980 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
......@@ -92,6 +93,7 @@ pub fn main() !void {
9293 var targets = ArrayList([]const u8).init(arena);
9394 var debug_log_scopes = ArrayList([]const u8).init(arena);
9495 var thread_pool_options: std.zig.ThreadPoolOptions = .{
96 .allocator = arena,
9597 .cache_directory = local_cache_directory,
9698 };
9799
......@@ -448,7 +450,8 @@ fn runStepNames(
448450 }
449451 }
450452
451 var thread_pool = try std.zig.initThreadPool(gpa, thread_pool_options);
453 const thread_pool = &b.graph.thread_pool;
454 try std.zig.initThreadPool(thread_pool, thread_pool_options);
452455 defer thread_pool.deinit();
453456
454457 {
......@@ -469,7 +472,7 @@ fn runStepNames(
469472 if (step.state == .skipped_oom) continue;
470473
471474 thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{
472 &wait_group, &thread_pool, b, step, step_prog, run,
475 &wait_group, b, step, step_prog, run,
473476 });
474477 }
475478 }
......@@ -890,12 +893,13 @@ fn constructGraphAndCheckForDependencyLoop(
890893
891894fn workerMakeOneStep(
892895 wg: *std.Thread.WaitGroup,
893 thread_pool: *std.Thread.Pool,
894896 b: *std.Build,
895897 s: *Step,
896898 prog_node: std.Progress.Node,
897899 run: *Run,
898900) void {
901 const thread_pool = &b.graph.thread_pool;
902
899903 // First, check the conditions for running this step. If they are not met,
900904 // then we return without doing the step, relying on another worker to
901905 // queue this step up again when dependencies are met.
......@@ -975,7 +979,7 @@ fn workerMakeOneStep(
975979 // Successful completion of a step, so we queue up its dependants as well.
976980 for (s.dependants.items) |dep| {
977981 thread_pool.spawnWg(wg, workerMakeOneStep, .{
978 wg, thread_pool, b, dep, prog_node, run,
982 wg, b, dep, prog_node, run,
979983 });
980984 }
981985 }
......@@ -1000,7 +1004,7 @@ fn workerMakeOneStep(
10001004 remaining -= dep.max_rss;
10011005
10021006 thread_pool.spawnWg(wg, workerMakeOneStep, .{
1003 wg, thread_pool, b, dep, prog_node, run,
1007 wg, b, dep, prog_node, run,
10041008 });
10051009 } else {
10061010 run.memory_blocked_steps.items[i] = dep;
lib/std/Build.zig+2
......@@ -120,6 +120,8 @@ pub const Graph = struct {
120120 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
121121 /// Information about the native target. Computed before build() is invoked.
122122 host: ResolvedTarget,
123 /// Uninitialized until the make phase.
124 thread_pool: std.Thread.Pool,
123125};
124126
125127const AvailableDeps = []const struct { []const u8, []const u8 };
lib/std/Build/Step.zig+5-2
......@@ -283,15 +283,17 @@ pub fn captureChildProcess(
283283 progress_node: std.Progress.Node,
284284 argv: []const []const u8,
285285) !std.process.Child.RunResult {
286 const arena = s.owner.allocator;
286 const b = s.owner;
287 const arena = b.allocator;
287288
288289 try handleChildProcUnsupported(s, null, argv);
289 try handleVerbose(s.owner, null, argv);
290 try handleVerbose(b, null, argv);
290291
291292 const result = std.process.Child.run(.{
292293 .allocator = arena,
293294 .argv = argv,
294295 .progress_node = progress_node,
296 .thread_pool = &b.graph.thread_pool,
295297 }) catch |err| return s.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
296298
297299 if (result.stderr.len > 0) {
......@@ -334,6 +336,7 @@ pub fn evalZigProcess(
334336 child.stderr_behavior = .Pipe;
335337 child.request_resource_usage_statistics = true;
336338 child.progress_node = prog_node;
339 child.thread_pool = &b.graph.thread_pool;
337340
338341 child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{
339342 argv[0], @errorName(err),
lib/std/Build/Step/Run.zig+1
......@@ -1246,6 +1246,7 @@ fn spawnChildAndCollect(
12461246 if (run.stdio != .zig_test and !run.disable_zig_progress and !inherit) {
12471247 child.progress_node = prog_node;
12481248 }
1249 child.thread_pool = &b.graph.thread_pool;
12491250
12501251 const term, const result, const elapsed_ns = t: {
12511252 if (inherit) std.debug.lockStdErr();
lib/std/Thread/Pool.zig+26-60
......@@ -7,11 +7,9 @@ const assert = std.debug.assert;
77mutex: std.Thread.Mutex,
88cond: std.Thread.Condition,
99run_queue: RunQueue,
10run_queue_len: usize,
1110end_flag: bool,
1211allocator: std.mem.Allocator,
13threads_buffer: []std.Thread,
14threads_len: usize,
12threads: []std.Thread,
1513job_server_options: Options.JobServer,
1614job_server: ?*JobServer,
1715
......@@ -23,6 +21,9 @@ const Runnable = struct {
2321const RunProto = *const fn (*Runnable) void;
2422
2523pub const Options = struct {
24 /// Not required to be thread-safe; protected by the pool's mutex.
25 allocator: std.mem.Allocator,
26
2627 /// Max number of threads to be actively working at the same time.
2728 ///
2829 /// `null` means to use the logical core count, leaving the main thread to
......@@ -49,22 +50,16 @@ pub const Options = struct {
4950 };
5051};
5152
52/// After initializing the thread pool and spawning work, the main thread must
53/// call `waitAndWork`.
54pub fn init(
55 /// Not required to be thread-safe; protected by the pool's mutex.
56 allocator: std.mem.Allocator,
57 options: Options,
58) !Pool {
59 var pool: Pool = .{
53pub fn init(pool: *Pool, options: Options) !void {
54 const allocator = options.allocator;
55
56 pool.* = .{
6057 .mutex = .{},
6158 .cond = .{},
6259 .run_queue = .{},
63 .run_queue_len = 0,
6460 .end_flag = false,
6561 .allocator = allocator,
66 .threads_buffer = &.{},
67 .threads_len = 0,
62 .threads = &.{},
6863 .job_server_options = options.job_server,
6964 .job_server = null,
7065 };
......@@ -75,8 +70,15 @@ pub fn init(
7570 const thread_count = options.n_jobs orelse @max(1, std.Thread.getCpuCount() catch 1);
7671 assert(thread_count > 0);
7772
78 pool.threads_buffer = try allocator.alloc(std.Thread, thread_count);
79 errdefer allocator.free(pool.threads_buffer);
73 // Kill and join any threads we spawned and free memory on error.
74 pool.threads = try allocator.alloc(std.Thread, thread_count);
75 var spawned: usize = 0;
76 errdefer pool.join(spawned);
77
78 for (pool.threads) |*thread| {
79 thread.* = try std.Thread.spawn(.{}, worker, .{pool});
80 spawned += 1;
81 }
8082
8183 switch (options.job_server) {
8284 .abstain, .connect => {},
......@@ -84,7 +86,7 @@ pub fn init(
8486 var server = try addr.listen(.{});
8587 errdefer server.deinit();
8688
87 const pollfds = try allocator.alloc(std.posix.pollfd, thread_count);
89 const pollfds = try allocator.alloc(std.posix.pollfd, thread_count + 1);
8890 errdefer allocator.free(pollfds);
8991
9092 const job_server = try allocator.create(JobServer);
......@@ -99,11 +101,14 @@ pub fn init(
99101 pool.job_server = job_server;
100102 },
101103 }
102
103 return pool;
104104}
105105
106106pub fn deinit(pool: *Pool) void {
107 pool.join(pool.threads.len);
108 pool.* = undefined;
109}
110
111fn join(pool: *Pool, spawned: usize) void {
107112 if (builtin.single_threaded)
108113 return;
109114
......@@ -127,15 +132,10 @@ pub fn deinit(pool: *Pool) void {
127132 job_server.thread.join();
128133 }
129134
130 // Since we set end_flag with the mutex locked, no more threads could have
131 // been created.
132 const threads = pool.threads_buffer[0..pool.threads_len];
133
134 for (threads) |thread|
135 for (pool.threads[0..spawned]) |thread|
135136 thread.join();
136137
137 pool.allocator.free(pool.threads_buffer);
138 pool.* = undefined;
138 pool.allocator.free(pool.threads);
139139}
140140
141141pub const JobServer = struct {
......@@ -256,22 +256,6 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
256256 };
257257
258258 pool.run_queue.prepend(&closure.run_node);
259 pool.run_queue_len += 1;
260
261 // If there was already any queued work, spawn a new thread if we are
262 // under the max.
263 if (pool.run_queue_len > 1 and pool.threads_len < pool.threads_buffer.len) {
264 if (std.Thread.spawn(.{}, worker, .{pool})) |new_thread| {
265 pool.threads_buffer[pool.threads_len] = new_thread;
266 pool.threads_len += 1;
267 } else |_| if (pool.threads_len == 0) {
268 pool.mutex.unlock();
269 @call(.auto, func, args);
270 wait_group.finish();
271 return;
272 }
273 }
274
275259 pool.mutex.unlock();
276260 }
277261
......@@ -319,21 +303,6 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) void {
319303 };
320304
321305 pool.run_queue.prepend(&closure.run_node);
322 pool.run_queue_len += 1;
323
324 // If there was already any queued work, spawn a new thread if we are
325 // under the max.
326 if (pool.run_queue_len > 1 and pool.threads_len < pool.threads_buffer.len) {
327 if (std.Thread.spawn(.{}, worker, .{pool})) |new_thread| {
328 pool.threads_buffer[pool.threads_len] = new_thread;
329 pool.threads_len += 1;
330 } else |_| if (pool.threads_len == 0) {
331 pool.mutex.unlock();
332 @call(.auto, func, args);
333 return;
334 }
335 }
336
337306 pool.mutex.unlock();
338307 }
339308
......@@ -351,8 +320,6 @@ fn worker(pool: *Pool) void {
351320
352321 while (true) {
353322 while (pool.run_queue.popFirst()) |run_node| {
354 pool.run_queue_len -= 1;
355
356323 // Temporarily unlock the mutex in order to execute the run_node.
357324 pool.mutex.unlock();
358325 defer pool.mutex.lock();
......@@ -391,7 +358,6 @@ pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
391358 defer pool.mutex.unlock();
392359 break :blk pool.run_queue.popFirst();
393360 }) |run_node| {
394 pool.run_queue_len -= 1;
395361 run_node.data.runFn(&run_node.data);
396362 continue;
397363 }
lib/std/process.zig+71-8
......@@ -1816,6 +1816,14 @@ pub const CreateEnvironOptions = struct {
18161816 /// If non-null, negative means to remove the environment variable, and >= 0
18171817 /// means to provide it with the given integer.
18181818 zig_progress_fd: ?i32 = null,
1819
1820 job_server_path: String = .unchanged,
1821
1822 pub const String = union(enum) {
1823 unchanged,
1824 deleted,
1825 updated: []const u8,
1826 };
18191827};
18201828
18211829/// Creates a null-deliminated environment variable block in the format
......@@ -1825,8 +1833,8 @@ pub fn createEnvironFromMap(
18251833 map: *const EnvMap,
18261834 options: CreateEnvironOptions,
18271835) Allocator.Error![:null]?[*:0]u8 {
1828 const ZigProgressAction = enum { nothing, edit, delete, add };
1829 const zig_progress_action: ZigProgressAction = a: {
1836 const EnvVarAction = enum { nothing, edit, delete, add };
1837 const zig_progress_action: EnvVarAction = a: {
18301838 const fd = options.zig_progress_fd orelse break :a .nothing;
18311839 const contains = map.get("ZIG_PROGRESS") != null;
18321840 if (fd >= 0) {
......@@ -1836,6 +1844,11 @@ pub fn createEnvironFromMap(
18361844 }
18371845 break :a .nothing;
18381846 };
1847 const job_server_action: EnvVarAction = switch (options.job_server_path) {
1848 .unchanged => .nothing,
1849 .deleted => if (map.get("JOBSERVERV2") != null) .delete else .nothing,
1850 .updated => if (map.get("JOBSERVERV2") != null) .edit else .add,
1851 };
18391852
18401853 const envp_count: usize = c: {
18411854 var count: usize = map.count();
......@@ -1844,6 +1857,11 @@ pub fn createEnvironFromMap(
18441857 .delete => count -= 1,
18451858 .nothing, .edit => {},
18461859 }
1860 switch (job_server_action) {
1861 .add => count += 1,
1862 .delete => count -= 1,
1863 .nothing, .edit => {},
1864 }
18471865 break :c count;
18481866 };
18491867
......@@ -1855,6 +1873,11 @@ pub fn createEnvironFromMap(
18551873 i += 1;
18561874 }
18571875
1876 if (job_server_action == .add) {
1877 envp_buf[i] = try std.fmt.allocPrintZ(arena, "JOBSERVERV2={s}", .{options.job_server_path.updated});
1878 i += 1;
1879 }
1880
18581881 {
18591882 var it = map.iterator();
18601883 while (it.next()) |pair| {
......@@ -1871,6 +1894,19 @@ pub fn createEnvironFromMap(
18711894 .nothing => {},
18721895 };
18731896
1897 if (mem.eql(u8, pair.key_ptr.*, "JOBSERVERV2")) switch (job_server_action) {
1898 .add => unreachable,
1899 .delete => continue,
1900 .edit => {
1901 envp_buf[i] = try std.fmt.allocPrintZ(arena, "{s}={s}", .{
1902 pair.key_ptr.*, options.job_server_path.updated,
1903 });
1904 i += 1;
1905 continue;
1906 },
1907 .nothing => {},
1908 };
1909
18741910 envp_buf[i] = try std.fmt.allocPrintZ(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* });
18751911 i += 1;
18761912 }
......@@ -1887,16 +1923,19 @@ pub fn createEnvironFromExisting(
18871923 existing: [*:null]const ?[*:0]const u8,
18881924 options: CreateEnvironOptions,
18891925) Allocator.Error![:null]?[*:0]u8 {
1890 const existing_count, const contains_zig_progress = c: {
1926 const existing_count, const contains_zig_progress, const contains_job_server = c: {
18911927 var count: usize = 0;
1892 var contains = false;
1928 var contains_zig_progress = false;
1929 var contains_job_server = false;
18931930 while (existing[count]) |line| : (count += 1) {
1894 contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS");
1931 const name = mem.sliceTo(line, '=');
1932 contains_zig_progress = contains_zig_progress or mem.eql(u8, name, "ZIG_PROGRESS");
1933 contains_job_server = contains_job_server or mem.eql(u8, name, "JOBSERVERV2");
18951934 }
1896 break :c .{ count, contains };
1935 break :c .{ count, contains_zig_progress, contains_job_server };
18971936 };
1898 const ZigProgressAction = enum { nothing, edit, delete, add };
1899 const zig_progress_action: ZigProgressAction = a: {
1937 const EnvVarAction = enum { nothing, edit, delete, add };
1938 const zig_progress_action: EnvVarAction = a: {
19001939 const fd = options.zig_progress_fd orelse break :a .nothing;
19011940 if (fd >= 0) {
19021941 break :a if (contains_zig_progress) .edit else .add;
......@@ -1905,6 +1944,11 @@ pub fn createEnvironFromExisting(
19051944 }
19061945 break :a .nothing;
19071946 };
1947 const job_server_action: EnvVarAction = switch (options.job_server_path) {
1948 .unchanged => .nothing,
1949 .deleted => if (contains_job_server) .delete else .nothing,
1950 .updated => if (contains_job_server) .edit else .add,
1951 };
19081952
19091953 const envp_count: usize = c: {
19101954 var count: usize = existing_count;
......@@ -1913,6 +1957,11 @@ pub fn createEnvironFromExisting(
19131957 .delete => count -= 1,
19141958 .nothing, .edit => {},
19151959 }
1960 switch (job_server_action) {
1961 .add => count += 1,
1962 .delete => count -= 1,
1963 .nothing, .edit => {},
1964 }
19161965 break :c count;
19171966 };
19181967
......@@ -1924,6 +1973,10 @@ pub fn createEnvironFromExisting(
19241973 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
19251974 i += 1;
19261975 }
1976 if (job_server_action == .add) {
1977 envp_buf[i] = try std.fmt.allocPrintZ(arena, "JOBSERVERV2={s}", .{options.job_server_path.updated});
1978 i += 1;
1979 }
19271980
19281981 while (existing[existing_index]) |line| : (existing_index += 1) {
19291982 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
......@@ -1936,6 +1989,16 @@ pub fn createEnvironFromExisting(
19361989 },
19371990 .nothing => {},
19381991 };
1992 if (mem.eql(u8, mem.sliceTo(line, '='), "JOBSERVERV2")) switch (job_server_action) {
1993 .add => unreachable,
1994 .delete => continue,
1995 .edit => {
1996 envp_buf[i] = try std.fmt.allocPrintZ(arena, "JOBSERVERV2={s}", .{options.job_server_path.updated});
1997 i += 1;
1998 continue;
1999 },
2000 .nothing => {},
2001 };
19392002 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
19402003 i += 1;
19412004 }
lib/std/process/Child.zig+28
......@@ -103,6 +103,25 @@ resource_usage_statistics: ResourceUsageStatistics = .{},
103103/// by substituting this node with the child's root node.
104104progress_node: std.Progress.Node = std.Progress.Node.none,
105105
106/// When provided, ensures that the child process will have access to the
107/// jobserver provided by the thread pool.
108///
109/// If the thread pool represents the root process, the child process will be
110/// supplied with the `JOBSERVERV2` environment variable so that it can
111/// connect.
112///
113/// If the thread pool represents a client, its connection address will be
114/// passed into the `JOBSERVERV2` environment variable. This potentially
115/// overrides the global environment variable.
116///
117/// If the thread pool is in abstinance mode, any `JOBSERVERV2` environment
118/// variable will be elided from being passed down to the child. This differs
119/// from leaving the field as `null` in which case no modifications to
120/// jobserver environment variables will occur.
121///
122/// A provided thread pool must live longer than this `Child` instance.
123thread_pool: ?*std.Thread.Pool = null,
124
106125pub const ResourceUsageStatistics = struct {
107126 rusage: @TypeOf(rusage_init) = rusage_init,
108127
......@@ -377,6 +396,7 @@ pub fn run(args: struct {
377396 max_output_bytes: usize = 50 * 1024,
378397 expand_arg0: Arg0Expand = .no_expand,
379398 progress_node: std.Progress.Node = std.Progress.Node.none,
399 thread_pool: ?*std.Thread.Pool = null,
380400}) RunError!RunResult {
381401 var child = ChildProcess.init(args.argv, args.allocator);
382402 child.stdin_behavior = .Ignore;
......@@ -387,6 +407,7 @@ pub fn run(args: struct {
387407 child.env_map = args.env_map;
388408 child.expand_arg0 = args.expand_arg0;
389409 child.progress_node = args.progress_node;
410 child.thread_pool = args.thread_pool;
390411
391412 var stdout = std.ArrayList(u8).init(args.allocator);
392413 var stderr = std.ArrayList(u8).init(args.allocator);
......@@ -616,19 +637,26 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
616637
617638 const envp: [*:null]const ?[*:0]const u8 = m: {
618639 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
640 const job_server_path: process.CreateEnvironOptions.String = if (self.thread_pool) |thread_pool| switch (thread_pool.job_server_options) {
641 .host, .connect => |addr| .{ .updated = mem.sliceTo(&addr.un.path, 0) },
642 .abstain => .deleted,
643 } else .unchanged;
619644 if (self.env_map) |env_map| {
620645 break :m (try process.createEnvironFromMap(arena, env_map, .{
621646 .zig_progress_fd = prog_fd,
647 .job_server_path = job_server_path,
622648 })).ptr;
623649 } else if (builtin.link_libc) {
624650 break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{
625651 .zig_progress_fd = prog_fd,
652 .job_server_path = job_server_path,
626653 })).ptr;
627654 } else if (builtin.output_mode == .Exe) {
628655 // Then we have Zig start code and this works.
629656 // TODO type-safety for null-termination of `os.environ`.
630657 break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{
631658 .zig_progress_fd = prog_fd,
659 .job_server_path = job_server_path,
632660 })).ptr;
633661 } else {
634662 // TODO come up with a solution for this.
lib/std/zig.zig+10-6
......@@ -689,7 +689,7 @@ pub const EnvVar = enum {
689689 CLICOLOR_FORCE,
690690 XDG_CACHE_HOME,
691691 HOME,
692 JOBSERVER2,
692 JOBSERVERV2,
693693
694694 pub fn isSet(comptime ev: EnvVar) bool {
695695 return std.process.hasEnvVarConstant(@tagName(ev));
......@@ -710,15 +710,17 @@ pub const EnvVar = enum {
710710};
711711
712712pub const ThreadPoolOptions = struct {
713 allocator: Allocator,
713714 n_jobs: ?u32 = null,
714715 cache_directory: std.Build.Cache.Directory,
715716};
716717
717718pub const cache_tmp_basename = "tmp";
718719
719pub fn initThreadPool(gpa: Allocator, options: ThreadPoolOptions) !std.Thread.Pool {
720 if (EnvVar.JOBSERVER2.getPosix()) |addr_string| {
721 return std.Thread.Pool.init(gpa, .{
720pub fn initThreadPool(thread_pool: *std.Thread.Pool, options: ThreadPoolOptions) !void {
721 if (EnvVar.JOBSERVERV2.getPosix()) |addr_string| {
722 return std.Thread.Pool.init(thread_pool, .{
723 .allocator = options.allocator,
722724 .n_jobs = options.n_jobs,
723725 .job_server = .{ .connect = try std.net.Address.initUnix(addr_string) },
724726 });
......@@ -744,13 +746,15 @@ pub fn initThreadPool(gpa: Allocator, options: ThreadPoolOptions) !std.Thread.Po
744746 @memcpy(addr.un.path[0..cache_dir.len], cache_dir);
745747 @memcpy(addr.un.path[cache_dir.len..][0..suffix.len], suffix);
746748
747 return std.Thread.Pool.init(gpa, .{
749 return std.Thread.Pool.init(thread_pool, .{
750 .allocator = options.allocator,
748751 .n_jobs = options.n_jobs,
749752 .job_server = .{ .host = addr },
750753 }) catch |err| switch (err) {
751754 error.FileNotFound => {
752755 try options.cache_directory.handle.makePath(cache_tmp_basename);
753 return std.Thread.Pool.init(gpa, .{
756 return std.Thread.Pool.init(thread_pool, .{
757 .allocator = options.allocator,
754758 .n_jobs = options.n_jobs,
755759 .job_server = .{ .host = addr },
756760 });
src/Compilation.zig+2
......@@ -4604,6 +4604,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
46044604 };
46054605 if (std.process.can_spawn) {
46064606 var child = std.process.Child.init(argv.items, arena);
4607 child.thread_pool = comp.thread_pool;
46074608 if (comp.clang_passthrough_mode) {
46084609 child.stdin_behavior = .Inherit;
46094610 child.stdout_behavior = .Inherit;
......@@ -4964,6 +4965,7 @@ fn spawnZigRc(
49644965 child.stdout_behavior = .Pipe;
49654966 child.stderr_behavior = .Pipe;
49664967 child.progress_node = child_progress_node;
4968 child.thread_pool = comp.thread_pool;
49674969
49684970 child.spawn() catch |err| {
49694971 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) });
src/link.zig+1
......@@ -1011,6 +1011,7 @@ pub fn spawnLld(
10111011 defer comp.gpa.free(stderr);
10121012
10131013 var child = std.process.Child.init(argv, arena);
1014 child.thread_pool = comp.thread_pool;
10141015 const term = (if (comp.clang_passthrough_mode) term: {
10151016 child.stdin_behavior = .Inherit;
10161017 child.stdout_behavior = .Inherit;
src/main.zig+16-4
......@@ -3136,7 +3136,9 @@ fn buildOutputType(
31363136 break :l global_cache_directory;
31373137 };
31383138
3139 var thread_pool = try std.zig.initThreadPool(gpa, .{
3139 var thread_pool: std.Thread.Pool = undefined;
3140 try std.zig.initThreadPool(&thread_pool, .{
3141 .allocator = gpa,
31403142 .cache_directory = local_cache_directory,
31413143 });
31423144 defer thread_pool.deinit();
......@@ -4250,6 +4252,7 @@ fn runOrTest(
42504252 child.stdin_behavior = .Inherit;
42514253 child.stdout_behavior = .Inherit;
42524254 child.stderr_behavior = .Inherit;
4255 child.thread_pool = comp.thread_pool;
42534256
42544257 // Here we release all the locks associated with the Compilation so
42554258 // that whatever this child process wants to do won't deadlock.
......@@ -4395,6 +4398,7 @@ fn runOrTestHotSwap(
43954398 child.stdin_behavior = .Inherit;
43964399 child.stdout_behavior = .Inherit;
43974400 child.stderr_behavior = .Inherit;
4401 child.thread_pool = comp.thread_pool;
43984402
43994403 try child.spawn();
44004404
......@@ -4896,7 +4900,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48964900
48974901 child_argv.items[argv_index_cache_dir] = local_cache_directory.path orelse cwd_path;
48984902
4899 var thread_pool = try std.zig.initThreadPool(gpa, .{
4903 var thread_pool: std.Thread.Pool = undefined;
4904 try std.zig.initThreadPool(&thread_pool, .{
4905 .allocator = gpa,
49004906 .cache_directory = local_cache_directory,
49014907 });
49024908 defer thread_pool.deinit();
......@@ -5185,6 +5191,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
51855191 child.stdin_behavior = .Inherit;
51865192 child.stdout_behavior = .Inherit;
51875193 child.stderr_behavior = .Inherit;
5194 child.thread_pool = &thread_pool;
51885195
51895196 const term = t: {
51905197 std.debug.lockStdErr();
......@@ -5331,7 +5338,9 @@ fn jitCmd(
53315338 };
53325339 defer global_cache_directory.handle.close();
53335340
5334 var thread_pool = try std.zig.initThreadPool(gpa, .{
5341 var thread_pool: std.Thread.Pool = undefined;
5342 try std.zig.initThreadPool(&thread_pool, .{
5343 .allocator = gpa,
53355344 .cache_directory = global_cache_directory,
53365345 });
53375346 defer thread_pool.deinit();
......@@ -5474,6 +5483,7 @@ fn jitCmd(
54745483 child.stdin_behavior = .Inherit;
54755484 child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe;
54765485 child.stderr_behavior = .Inherit;
5486 child.thread_pool = &thread_pool;
54775487
54785488 try child.spawn();
54795489
......@@ -6897,7 +6907,9 @@ fn cmdFetch(
68976907 };
68986908 defer global_cache_directory.handle.close();
68996909
6900 var thread_pool = try std.zig.initThreadPool(gpa, .{
6910 var thread_pool: std.Thread.Pool = undefined;
6911 try std.zig.initThreadPool(&thread_pool, .{
6912 .allocator = gpa,
69016913 .cache_directory = global_cache_directory,
69026914 });
69036915 defer thread_pool.deinit();