authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-12 22:42:03-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-13 11:32:46+02:00
log99bfd07854b4e14e01e32e83302707cacde15cb5
treeff995937616902f03c0aa5517f592bef134ba8d7
parent470f77600d79a5c17b3734f97eaf2462c452718e

Maker: deinit Watch when exiting scope

this wasn't really needed before but now it's pretty important since configuration needs to be rerun sometimes

2 files changed, 76 insertions(+), 18 deletions(-)

lib/compiler/Maker.zig+16-14
...@@ -225,7 +225,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -225,7 +225,7 @@ pub fn main(init: process.Init.Minimal) !void {
225 var skip_oom_steps = false;225 var skip_oom_steps = false;
226 var test_timeout_ns: ?u64 = null;226 var test_timeout_ns: ?u64 = null;
227 var color: Color = .settingFromEnvironment(&graph.environ_map);227 var color: Color = .settingFromEnvironment(&graph.environ_map);
228 var watch = false;228 var watch_flag = false;
229 var fuzz: ?Fuzz.Mode = null;229 var fuzz: ?Fuzz.Mode = null;
230 var debounce_interval_ms: u16 = 50;230 var debounce_interval_ms: u16 = 50;
231 var listen: bool = false;231 var listen: bool = false;
...@@ -474,7 +474,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -474,7 +474,7 @@ pub fn main(init: process.Init.Minimal) !void {
474 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {474 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
475 graph.verbose_llvm_ir = true;475 graph.verbose_llvm_ir = true;
476 } else if (mem.eql(u8, arg, "--watch")) {476 } else if (mem.eql(u8, arg, "--watch")) {
477 watch = true;477 watch_flag = true;
478 } else if (mem.eql(u8, arg, "--time-report")) {478 } else if (mem.eql(u8, arg, "--time-report")) {
479 graph.time_report = true;479 graph.time_report = true;
480 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };480 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
...@@ -574,7 +574,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -574,7 +574,7 @@ pub fn main(init: process.Init.Minimal) !void {
574 }574 }
575575
576 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;576 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;
577 const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null or listen);577 const server_mode = !early_exit_mode and (watch_flag or webui_listen != null or fuzz != null or listen);
578578
579 process.raiseFileDescriptorLimit();579 process.raiseFileDescriptorLimit();
580580
...@@ -701,7 +701,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -701,7 +701,7 @@ pub fn main(init: process.Init.Minimal) !void {
701 var protocol_server_allocation: AvoidableServer = undefined;701 var protocol_server_allocation: AvoidableServer = undefined;
702 const protocol_server: ?*AvoidableServer = if (listen) s: {702 const protocol_server: ?*AvoidableServer = if (listen) s: {
703 if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{});703 if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{});
704 if (watch) fatal("using '--watch' and '--listen' together is not supported", .{});704 if (watch_flag) fatal("using '--watch' and '--listen' together is not supported", .{});
705 if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{});705 if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{});
706 if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{});706 if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{});
707 protocol_server_allocation = .{707 protocol_server_allocation = .{
...@@ -788,7 +788,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -788,7 +788,7 @@ pub fn main(init: process.Init.Minimal) !void {
788 .skip_oom_steps = skip_oom_steps,788 .skip_oom_steps = skip_oom_steps,
789 .unit_test_timeout_ns = test_timeout_ns,789 .unit_test_timeout_ns = test_timeout_ns,
790790
791 .watch = watch,791 .watch = watch_flag,
792 .web_server = web_server,792 .web_server = web_server,
793 .protocol_server = protocol_server,793 .protocol_server = protocol_server,
794 .protocol_server_mutex = .init,794 .protocol_server_mutex = .init,
...@@ -801,7 +801,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -801,7 +801,7 @@ pub fn main(init: process.Init.Minimal) !void {
801 .multiline_errors = multiline_errors,801 .multiline_errors = multiline_errors,
802 .summary = summary orelse if (listen)802 .summary = summary orelse if (listen)
803 .none803 .none
804 else if (watch or webui_listen != null)804 else if (watch_flag or webui_listen != null)
805 .new805 .new
806 else806 else
807 .failures,807 .failures,
...@@ -820,7 +820,8 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -820,7 +820,8 @@ pub fn main(init: process.Init.Minimal) !void {
820 if (protocol_server) |s| {820 if (protocol_server) |s| {
821 try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path}));821 try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path}));
822822
823 var w: ?Watch = null;823 var watch: ?Watch = null;
824 defer if (watch) |*w| w.deinit();
824825
825 const Event = union(enum) {826 const Event = union(enum) {
826 message: Reader.Error!Client.Message.Header,827 message: Reader.Error!Client.Message.Header,
...@@ -869,11 +870,11 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -869,11 +870,11 @@ pub fn main(init: process.Init.Minimal) !void {
869870
870 if (body.flags.watch) {871 if (body.flags.watch) {
871 if (!Watch.have_impl) unreachable;872 if (!Watch.have_impl) unreachable;
872 if (w == null) w = try .init(&maker);873 if (watch == null) watch = try .init(&maker);
873874
874 try updateWatch(&maker, &w.?);875 try updateWatch(&maker, &watch.?);
875 try select.concurrent(.fs_event, Watch.wait, .{876 try select.concurrent(.fs_event, Watch.wait, .{
876 &w.?,877 &watch.?,
877 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,878 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,
878 });879 });
879 }880 }
...@@ -902,7 +903,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -902,7 +903,7 @@ pub fn main(init: process.Init.Minimal) !void {
902 .clean => {},903 .clean => {},
903 }904 }
904 try select.concurrent(.fs_event, Watch.wait, .{905 try select.concurrent(.fs_event, Watch.wait, .{
905 &w.?,906 &watch.?,
906 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,907 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,
907 });908 });
908 continue :loop try select.await();909 continue :loop try select.await();
...@@ -924,10 +925,11 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -924,10 +925,11 @@ pub fn main(init: process.Init.Minimal) !void {
924 };925 };
925926
926 var w: Watch = w: {927 var w: Watch = w: {
927 if (!watch) break :w undefined;928 if (!watch_flag) break :w undefined;
928 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});929 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});
929 break :w try .init(&maker);930 break :w try .init(&maker);
930 };931 };
932 defer w.deinit();
931933
932 if (web_server) |ws| try ws.updateConfiguration(&maker);934 if (web_server) |ws| try ws.updateConfiguration(&maker);
933935
...@@ -942,7 +944,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -942,7 +944,7 @@ pub fn main(init: process.Init.Minimal) !void {
942944
943 if (web_server) |ws| {945 if (web_server) |ws| {
944 const c = &scanned_config.configuration;946 const c = &scanned_config.configuration;
945 assert(!watch); // fatal error after CLI parsing947 assert(!watch_flag); // fatal error after CLI parsing
946 while (true) switch (try ws.wait()) {948 while (true) switch (try ws.wait()) {
947 .rebuild => {949 .rebuild => {
948 for (maker.step_stack.keys()) |step_index| {950 for (maker.step_stack.keys()) |step_index| {
...@@ -1019,7 +1021,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -1019,7 +1021,7 @@ pub fn main(init: process.Init.Minimal) !void {
1019 if (protocol_server != null) {1021 if (protocol_server != null) {
1020 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});1022 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});
1021 }1023 }
1022 if (watch and can_fs_watch) {1024 if (watch_flag and can_fs_watch) {
1023 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});1025 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});
1024 } else {1026 } else {
1025 fatal("(zig build system) TODO stay running and wait for user to request rebuild even when build.zig compilation fails", .{});1027 fatal("(zig build system) TODO stay running and wait for user to request rebuild even when build.zig compilation fails", .{});
lib/compiler/Maker/Watch.zig+60-4
...@@ -84,7 +84,7 @@ const Os = switch (builtin.os.tag) {...@@ -84,7 +84,7 @@ const Os = switch (builtin.os.tag) {
84 }84 }
8585
86 fn destroy(lfh: FileHandle, gpa: Allocator) void {86 fn destroy(lfh: FileHandle, gpa: Allocator) void {
87 const ptr: [*]u8 = @ptrCast(lfh.handle);87 const ptr: [*]align(@alignOf(std.os.linux.file_handle)) u8 = @ptrCast(@alignCast(lfh.handle));
88 const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes];88 const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes];
89 return gpa.free(allocated_slice);89 return gpa.free(allocated_slice);
90 }90 }
...@@ -124,6 +124,24 @@ const Os = switch (builtin.os.tag) {...@@ -124,6 +124,24 @@ const Os = switch (builtin.os.tag) {
124 };124 };
125 }125 }
126126
127 fn deinit(w: *Watch) void {
128 const gpa = w.maker.gpa;
129
130 for (w.os.handle_table.keys(), w.os.handle_table.values()) |fh, *reaction| {
131 fh.destroy(gpa);
132 reaction.reaction_set.deinit(gpa);
133 }
134 w.os.handle_table.deinit(gpa);
135
136 for (w.os.poll_fds.values()) |pollfd| {
137 Io.Threaded.closeFd(pollfd.fd);
138 }
139 w.os.poll_fds.deinit(gpa);
140
141 w.dir_table.deinit(gpa);
142 w.* = undefined;
143 }
144
127 fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle {145 fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle {
128 var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined;146 var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined;
129 var buf: [std.fs.max_path_bytes]u8 = undefined;147 var buf: [std.fs.max_path_bytes]u8 = undefined;
...@@ -365,7 +383,7 @@ const Os = switch (builtin.os.tag) {...@@ -365,7 +383,7 @@ const Os = switch (builtin.os.tag) {
365 }383 }
366 }384 }
367385
368 fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(std.Io.Threaded.apc_align) callconv(.winapi) void {386 fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(Io.Threaded.apc_align) callconv(.winapi) void {
369 const w: *Watch = @ptrCast(@alignCast(apc_context));387 const w: *Watch = @ptrCast(@alignCast(apc_context));
370 const dir: *Directory = @fieldParentPtr("iosb", iosb);388 const dir: *Directory = @fieldParentPtr("iosb", iosb);
371 assert(iosb.u.Status != .PENDING);389 assert(iosb.u.Status != .PENDING);
...@@ -485,6 +503,18 @@ const Os = switch (builtin.os.tag) {...@@ -485,6 +503,18 @@ const Os = switch (builtin.os.tag) {
485 };503 };
486 }504 }
487505
506 fn deinit(w: *Watch) void {
507 const gpa = w.maker.gpa;
508
509 for (w.os.handle_table.keys()) |dir| {
510 dir.deinit(gpa, w);
511 }
512 w.os.handle_table.deinit(gpa);
513
514 w.dir_table.deinit(gpa);
515 w.* = undefined;
516 }
517
488 fn getFileId(handle: windows.HANDLE) !FileId {518 fn getFileId(handle: windows.HANDLE) !FileId {
489 var file_id: FileId = undefined;519 var file_id: FileId = undefined;
490 var io_status: windows.IO_STATUS_BLOCK = undefined;520 var io_status: windows.IO_STATUS_BLOCK = undefined;
...@@ -695,6 +725,21 @@ const Os = switch (builtin.os.tag) {...@@ -695,6 +725,21 @@ const Os = switch (builtin.os.tag) {
695 };725 };
696 }726 }
697727
728 fn deinit(w: *Watch) void {
729 const gpa = w.maker.gpa;
730
731 for (w.os.handles.items(.rs), w.os.handles.items(.dir_fd)) |rs, dir_fd| {
732 rs.deinit(gpa);
733 Os.Threaded.closeFd(dir_fd);
734 }
735 w.os.handles.deinit(gpa);
736
737 Os.Threaded.closeFd(w.os.kq_fd);
738
739 w.dir_table.deinit(gpa);
740 w.* = undefined;
741 }
742
698 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {743 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
699 const maker = w.maker;744 const maker = w.maker;
700 const gpa = maker.gpa;745 const gpa = maker.gpa;
...@@ -713,7 +758,7 @@ const Os = switch (builtin.os.tag) {...@@ -713,7 +758,7 @@ const Os = switch (builtin.os.tag) {
713 fatal("failed to open directory {f}: {t}", .{ path, err });758 fatal("failed to open directory {f}: {t}", .{ path, err });
714 };759 };
715 // Empirically the dir has to stay open or else no events are triggered.760 // Empirically the dir has to stay open or else no events are triggered.
716 errdefer if (!skip_open_dir) std.Io.Threaded.closeFd(dir_fd);761 errdefer if (!skip_open_dir) Io.Threaded.closeFd(dir_fd);
717 const changes = [1]posix.Kevent{.{762 const changes = [1]posix.Kevent{.{
718 .ident = @bitCast(@as(isize, dir_fd)),763 .ident = @bitCast(@as(isize, dir_fd)),
719 .filter = std.c.EVFILT.VNODE,764 .filter = std.c.EVFILT.VNODE,
...@@ -813,7 +858,7 @@ const Os = switch (builtin.os.tag) {...@@ -813,7 +858,7 @@ const Os = switch (builtin.os.tag) {
813 };858 };
814 const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes;859 const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes;
815 _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null);860 _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null);
816 if (path.sub_path.len != 0) std.Io.Threaded.closeFd(dir_fd);861 if (path.sub_path.len != 0) Io.Threaded.closeFd(dir_fd);
817862
818 w.dir_table.swapRemoveAt(i);863 w.dir_table.swapRemoveAt(i);
819 handles.swapRemove(i);864 handles.swapRemove(i);
...@@ -877,6 +922,13 @@ const Os = switch (builtin.os.tag) {...@@ -877,6 +922,13 @@ const Os = switch (builtin.os.tag) {
877 .maker = maker,922 .maker = maker,
878 };923 };
879 }924 }
925 fn deinit(w: *Watch) void {
926 const gpa = w.maker.gpa;
927 const io = w.maker.io;
928 w.os.fse.deinit(gpa, io);
929 w.dir_table.deinit(gpa);
930 w.* = undefined;
931 }
880 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {932 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
881 try w.os.fse.setPaths(w.maker, steps);933 try w.os.fse.setPaths(w.maker, steps);
882 w.dir_count = w.os.fse.watch_roots.len;934 w.dir_count = w.os.fse.watch_roots.len;
...@@ -970,3 +1022,7 @@ pub const WaitResult = enum {...@@ -970,3 +1022,7 @@ pub const WaitResult = enum {
970pub fn wait(w: *Watch, timeout: Timeout) !WaitResult {1022pub fn wait(w: *Watch, timeout: Timeout) !WaitResult {
971 return Os.wait(w, timeout);1023 return Os.wait(w, timeout);
972}1024}
1025
1026pub fn deinit(w: *Watch) void {
1027 Os.deinit(w);
1028}