From 99bfd07854b4e14e01e32e83302707cacde15cb5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 12 Aug 2026 22:42:03 -0700 Subject: [PATCH] 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 --- lib/compiler/Maker.zig | 30 +++++++++-------- lib/compiler/Maker/Watch.zig | 64 +++++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 0b10b8fdacfa220a87b9e078d2067c9fb468a99d..06b4286cb39b58ab82bcc4ea3b6333fbea8f719b 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -225,7 +225,7 @@ pub fn main(init: process.Init.Minimal) !void { var skip_oom_steps = false; var test_timeout_ns: ?u64 = null; var color: Color = .settingFromEnvironment(&graph.environ_map); - var watch = false; + var watch_flag = false; var fuzz: ?Fuzz.Mode = null; var debounce_interval_ms: u16 = 50; var listen: bool = false; @@ -474,7 +474,7 @@ pub fn main(init: process.Init.Minimal) !void { } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { graph.verbose_llvm_ir = true; } else if (mem.eql(u8, arg, "--watch")) { - watch = true; + watch_flag = true; } else if (mem.eql(u8, arg, "--time-report")) { graph.time_report = true; if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; @@ -574,7 +574,7 @@ pub fn main(init: process.Init.Minimal) !void { } const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none; - const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null or listen); + const server_mode = !early_exit_mode and (watch_flag or webui_listen != null or fuzz != null or listen); process.raiseFileDescriptorLimit(); @@ -701,7 +701,7 @@ pub fn main(init: process.Init.Minimal) !void { var protocol_server_allocation: AvoidableServer = undefined; const protocol_server: ?*AvoidableServer = if (listen) s: { if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{}); - if (watch) fatal("using '--watch' and '--listen' together is not supported", .{}); + if (watch_flag) fatal("using '--watch' and '--listen' together is not supported", .{}); if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{}); if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{}); protocol_server_allocation = .{ @@ -788,7 +788,7 @@ pub fn main(init: process.Init.Minimal) !void { .skip_oom_steps = skip_oom_steps, .unit_test_timeout_ns = test_timeout_ns, - .watch = watch, + .watch = watch_flag, .web_server = web_server, .protocol_server = protocol_server, .protocol_server_mutex = .init, @@ -801,7 +801,7 @@ pub fn main(init: process.Init.Minimal) !void { .multiline_errors = multiline_errors, .summary = summary orelse if (listen) .none - else if (watch or webui_listen != null) + else if (watch_flag or webui_listen != null) .new else .failures, @@ -820,7 +820,8 @@ pub fn main(init: process.Init.Minimal) !void { if (protocol_server) |s| { try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path})); - var w: ?Watch = null; + var watch: ?Watch = null; + defer if (watch) |*w| w.deinit(); const Event = union(enum) { message: Reader.Error!Client.Message.Header, @@ -869,11 +870,11 @@ pub fn main(init: process.Init.Minimal) !void { if (body.flags.watch) { if (!Watch.have_impl) unreachable; - if (w == null) w = try .init(&maker); + if (watch == null) watch = try .init(&maker); - try updateWatch(&maker, &w.?); + try updateWatch(&maker, &watch.?); try select.concurrent(.fs_event, Watch.wait, .{ - &w.?, + &watch.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none, }); } @@ -902,7 +903,7 @@ pub fn main(init: process.Init.Minimal) !void { .clean => {}, } try select.concurrent(.fs_event, Watch.wait, .{ - &w.?, + &watch.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none, }); continue :loop try select.await(); @@ -924,10 +925,11 @@ pub fn main(init: process.Init.Minimal) !void { }; var w: Watch = w: { - if (!watch) break :w undefined; + if (!watch_flag) break :w undefined; if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os}); break :w try .init(&maker); }; + defer w.deinit(); if (web_server) |ws| try ws.updateConfiguration(&maker); @@ -942,7 +944,7 @@ pub fn main(init: process.Init.Minimal) !void { if (web_server) |ws| { const c = &scanned_config.configuration; - assert(!watch); // fatal error after CLI parsing + assert(!watch_flag); // fatal error after CLI parsing while (true) switch (try ws.wait()) { .rebuild => { for (maker.step_stack.keys()) |step_index| { @@ -1019,7 +1021,7 @@ pub fn main(init: process.Init.Minimal) !void { if (protocol_server != null) { fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{}); } - if (watch and can_fs_watch) { + if (watch_flag and can_fs_watch) { fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{}); } else { fatal("(zig build system) TODO stay running and wait for user to request rebuild even when build.zig compilation fails", .{}); diff --git a/lib/compiler/Maker/Watch.zig b/lib/compiler/Maker/Watch.zig index 6d3fd41ad9baa30b6e26195f1de015e359d3248e..c9af991a4bfc7bd6352a8bfeb5f314527de6fb5e 100644 --- a/lib/compiler/Maker/Watch.zig +++ b/lib/compiler/Maker/Watch.zig @@ -84,7 +84,7 @@ const Os = switch (builtin.os.tag) { } fn destroy(lfh: FileHandle, gpa: Allocator) void { - const ptr: [*]u8 = @ptrCast(lfh.handle); + const ptr: [*]align(@alignOf(std.os.linux.file_handle)) u8 = @ptrCast(@alignCast(lfh.handle)); const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes]; return gpa.free(allocated_slice); } @@ -124,6 +124,24 @@ const Os = switch (builtin.os.tag) { }; } + fn deinit(w: *Watch) void { + const gpa = w.maker.gpa; + + for (w.os.handle_table.keys(), w.os.handle_table.values()) |fh, *reaction| { + fh.destroy(gpa); + reaction.reaction_set.deinit(gpa); + } + w.os.handle_table.deinit(gpa); + + for (w.os.poll_fds.values()) |pollfd| { + Io.Threaded.closeFd(pollfd.fd); + } + w.os.poll_fds.deinit(gpa); + + w.dir_table.deinit(gpa); + w.* = undefined; + } + fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle { var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined; var buf: [std.fs.max_path_bytes]u8 = undefined; @@ -365,7 +383,7 @@ const Os = switch (builtin.os.tag) { } } - fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(std.Io.Threaded.apc_align) callconv(.winapi) void { + fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(Io.Threaded.apc_align) callconv(.winapi) void { const w: *Watch = @ptrCast(@alignCast(apc_context)); const dir: *Directory = @fieldParentPtr("iosb", iosb); assert(iosb.u.Status != .PENDING); @@ -485,6 +503,18 @@ const Os = switch (builtin.os.tag) { }; } + fn deinit(w: *Watch) void { + const gpa = w.maker.gpa; + + for (w.os.handle_table.keys()) |dir| { + dir.deinit(gpa, w); + } + w.os.handle_table.deinit(gpa); + + w.dir_table.deinit(gpa); + w.* = undefined; + } + fn getFileId(handle: windows.HANDLE) !FileId { var file_id: FileId = undefined; var io_status: windows.IO_STATUS_BLOCK = undefined; @@ -695,6 +725,21 @@ const Os = switch (builtin.os.tag) { }; } + fn deinit(w: *Watch) void { + const gpa = w.maker.gpa; + + for (w.os.handles.items(.rs), w.os.handles.items(.dir_fd)) |rs, dir_fd| { + rs.deinit(gpa); + Os.Threaded.closeFd(dir_fd); + } + w.os.handles.deinit(gpa); + + Os.Threaded.closeFd(w.os.kq_fd); + + w.dir_table.deinit(gpa); + w.* = undefined; + } + fn update(w: *Watch, steps: []const Configuration.Step.Index) !void { const maker = w.maker; const gpa = maker.gpa; @@ -713,7 +758,7 @@ const Os = switch (builtin.os.tag) { fatal("failed to open directory {f}: {t}", .{ path, err }); }; // Empirically the dir has to stay open or else no events are triggered. - errdefer if (!skip_open_dir) std.Io.Threaded.closeFd(dir_fd); + errdefer if (!skip_open_dir) Io.Threaded.closeFd(dir_fd); const changes = [1]posix.Kevent{.{ .ident = @bitCast(@as(isize, dir_fd)), .filter = std.c.EVFILT.VNODE, @@ -813,7 +858,7 @@ const Os = switch (builtin.os.tag) { }; const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes; _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null); - if (path.sub_path.len != 0) std.Io.Threaded.closeFd(dir_fd); + if (path.sub_path.len != 0) Io.Threaded.closeFd(dir_fd); w.dir_table.swapRemoveAt(i); handles.swapRemove(i); @@ -877,6 +922,13 @@ const Os = switch (builtin.os.tag) { .maker = maker, }; } + fn deinit(w: *Watch) void { + const gpa = w.maker.gpa; + const io = w.maker.io; + w.os.fse.deinit(gpa, io); + w.dir_table.deinit(gpa); + w.* = undefined; + } fn update(w: *Watch, steps: []const Configuration.Step.Index) !void { try w.os.fse.setPaths(w.maker, steps); w.dir_count = w.os.fse.watch_roots.len; @@ -970,3 +1022,7 @@ pub const WaitResult = enum { pub fn wait(w: *Watch, timeout: Timeout) !WaitResult { return Os.wait(w, timeout); } + +pub fn deinit(w: *Watch) void { + Os.deinit(w); +} -- 2.54.0