diff --git a/src/backend/KQueue.zig b/src/backend/KQueue.zig index 4cbe60f..923a6b0 100644 --- a/src/backend/KQueue.zig +++ b/src/backend/KQueue.zig @@ -93,7 +93,7 @@ pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { self.file_watches.deinit(allocator); var sit = self.snapshots.iterator(); while (sit.next()) |entry| { - // Keys are borrowed from self.watches and freed in the watches loop above. + allocator.free(entry.key_ptr.*); // independently owned; see take_snapshot/scan_dir var names = entry.value_ptr.*; var nit = names.iterator(); while (nit.next()) |ne| allocator.free(ne.key_ptr.*); @@ -436,7 +436,15 @@ fn take_snapshot(self: *@This(), allocator: std.mem.Allocator, dir_path: []const self.snapshots_mutex.lock(); errdefer self.snapshots_mutex.unlock(); const gop = try self.snapshots.getOrPut(allocator, dir_path); - if (!gop.found_existing) gop.value_ptr.* = .empty; + if (!gop.found_existing) { + // Snapshot outer keys are independently owned so they can be safely + // freed in deinit/remove_watch regardless of how the entry was created. + gop.key_ptr.* = allocator.dupe(u8, dir_path) catch |e| { + _ = self.snapshots.remove(dir_path); + return e; + }; + gop.value_ptr.* = .empty; + } var snapshot = gop.value_ptr; for (names.items) |name| { if (snapshot.contains(name)) continue; @@ -464,6 +472,7 @@ pub fn remove_watch(self: *@This(), allocator: std.mem.Allocator, path: []const const snap_entry = self.snapshots.fetchRemove(path); self.snapshots_mutex.unlock(); if (snap_entry) |entry| { + allocator.free(entry.key); // independently owned; see take_snapshot/scan_dir var names = entry.value; var it = names.iterator(); while (it.next()) |ne| { diff --git a/src/backend/KQueueDir.zig b/src/backend/KQueueDir.zig index ee8e0b2..a114388 100644 --- a/src/backend/KQueueDir.zig +++ b/src/backend/KQueueDir.zig @@ -78,7 +78,7 @@ pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { self.watches.deinit(allocator); var sit = self.snapshots.iterator(); while (sit.next()) |entry| { - // Keys are borrowed from self.watches and freed in the watches loop above. + allocator.free(entry.key_ptr.*); // independently owned; see take_snapshot/scan_dir var snap = entry.value_ptr.*; var nit = snap.iterator(); while (nit.next()) |ne| allocator.free(ne.key_ptr.*); @@ -385,7 +385,15 @@ fn take_snapshot(self: *@This(), allocator: std.mem.Allocator, dir_path: []const self.snapshots_mutex.lock(); errdefer self.snapshots_mutex.unlock(); const gop = try self.snapshots.getOrPut(allocator, dir_path); - if (!gop.found_existing) gop.value_ptr.* = .empty; + if (!gop.found_existing) { + // Snapshot outer keys are independently owned so they can be safely + // freed in deinit/remove_watch regardless of how the entry was created. + gop.key_ptr.* = allocator.dupe(u8, dir_path) catch |e| { + _ = self.snapshots.remove(dir_path); + return e; + }; + gop.value_ptr.* = .empty; + } const snapshot = gop.value_ptr; for (entries.items) |e| { if (snapshot.contains(e.name)) continue; @@ -407,6 +415,7 @@ pub fn remove_watch(self: *@This(), allocator: std.mem.Allocator, path: []const const snap_entry = self.snapshots.fetchRemove(path); self.snapshots_mutex.unlock(); if (snap_entry) |entry| { + allocator.free(entry.key); // independently owned; see take_snapshot/scan_dir var snap = entry.value; var it = snap.iterator(); while (it.next()) |ne| allocator.free(ne.key_ptr.*); diff --git a/src/backend/Windows.zig b/src/backend/Windows.zig index 89bd94c..ea4f046 100644 --- a/src/backend/Windows.zig +++ b/src/backend/Windows.zig @@ -210,7 +210,8 @@ fn thread_fn( } // Re-arm ReadDirectoryChangesW for the next batch. w.overlapped = std.mem.zeroes(windows.OVERLAPPED); - _ = win32.ReadDirectoryChangesW(w.handle, w.buf.ptr, buf_size, 1, notify_filter, null, &w.overlapped, null); + if (win32.ReadDirectoryChangesW(w.handle, w.buf.ptr, buf_size, 1, notify_filter, null, &w.overlapped, null) == 0) + std.log.err("nightwatch: ReadDirectoryChangesW re-arm failed for {s}, future events lost", .{entry.key_ptr.*}); break; } watches_mutex.unlock(); diff --git a/src/nightwatch.zig b/src/nightwatch.zig index 17cb5a3..633ef76 100644 --- a/src/nightwatch.zig +++ b/src/nightwatch.zig @@ -225,7 +225,8 @@ pub fn Create(comptime variant: Variant) type { fn change_cb(h: *Handler, path: []const u8, event_type: EventType, object_type: ObjectType) error{HandlerFailed}!void { const self: *Interceptor = @fieldParentPtr("handler", h); if (event_type == .created and object_type == .dir and !Backend.watches_recursively) { - self.backend.add_watch(self.allocator, path) catch {}; + self.backend.add_watch(self.allocator, path) catch |e| + std.log.err("nightwatch: add_watch failed for {s}: {s}", .{ path, @errorName(e) }); recurse_watch(&self.backend, self.allocator, path); } return self.user_handler.change(path, event_type, object_type); @@ -254,7 +255,8 @@ pub fn Create(comptime variant: Variant) type { fn change_cb(h: *PollingHandler, path: []const u8, event_type: EventType, object_type: ObjectType) error{HandlerFailed}!void { const self: *PollingInterceptor = @fieldParentPtr("handler", h); if (event_type == .created and object_type == .dir and !Backend.watches_recursively) { - self.backend.add_watch(self.allocator, path) catch {}; + self.backend.add_watch(self.allocator, path) catch |e| + std.log.err("nightwatch: add_watch failed for {s}: {s}", .{ path, @errorName(e) }); recurse_watch(&self.backend, self.allocator, path); } return self.user_handler.change(path, event_type, object_type); @@ -280,7 +282,8 @@ pub fn Create(comptime variant: Variant) type { if (entry.kind != .directory) continue; var buf: [std.fs.max_path_bytes]u8 = undefined; const sub = std.fmt.bufPrint(&buf, "{s}{c}{s}", .{ dir_path, std.fs.path.sep, entry.name }) catch continue; - backend.add_watch(allocator, sub) catch {}; + backend.add_watch(allocator, sub) catch |e| + std.log.err("nightwatch: add_watch failed for {s}: {s}", .{ sub, @errorName(e) }); recurse_watch(backend, allocator, sub); } }