diff --git a/src/backend/FSEvents.zig b/src/backend/FSEvents.zig index 10094c7..53dc69d 100644 --- a/src/backend/FSEvents.zig +++ b/src/backend/FSEvents.zig @@ -220,6 +220,11 @@ fn callback( // a single event. Emit one change call per applicable flag so // callers see all relevant event types (e.g. created + modified). const ot: ObjectType = if (flags & kFSEventStreamEventFlagItemIsDir != 0) .dir else .file; + // Handler errors are silently ignored: this callback runs on a GCD + // dispatch thread managed by the OS, so there is no way to propagate + // an error back to the caller. Stopping the stream from inside the + // callback would require a separate signal channel and is not worth + // the complexity; the stream will keep delivering future events. if (flags & kFSEventStreamEventFlagItemCreated != 0) { ctx.handler.change(path, .created, ot) catch {}; } diff --git a/src/backend/INotify.zig b/src/backend/INotify.zig index c71692b..db2864d 100644 --- a/src/backend/INotify.zig +++ b/src/backend/INotify.zig @@ -118,7 +118,10 @@ pub fn Create(comptime variant: InterfaceType) type { .{ .fd = self.stop_pipe[0], .events = std.posix.POLL.IN, .revents = 0 }, }; while (true) { - _ = std.posix.poll(&pfds, -1) catch return; + _ = std.posix.poll(&pfds, -1) catch |e| { + std.log.err("nightwatch: poll failed: {s}, stopping watch thread", .{@errorName(e)}); + return; + }; if (pfds[1].revents & std.posix.POLL.IN != 0) return; // stop signal if (pfds[0].revents & std.posix.POLL.IN != 0) { self.handle_read_ready(allocator) catch |e| { diff --git a/src/backend/KQueue.zig b/src/backend/KQueue.zig index 97fbdff..4cbe60f 100644 --- a/src/backend/KQueue.zig +++ b/src/backend/KQueue.zig @@ -112,7 +112,10 @@ fn thread_fn(self: *@This(), allocator: std.mem.Allocator) void { var events: [64]std.posix.Kevent = undefined; while (true) { // Block indefinitely until kqueue has events. - const n = std.posix.kevent(self.kq, &.{}, &events, null) catch break; + const n = std.posix.kevent(self.kq, &.{}, &events, null) catch |e| { + std.log.err("nightwatch: kevent failed: {s}, stopping watch thread", .{@errorName(e)}); + break; + }; for (events[0..n]) |ev| { if (ev.filter == EVFILT_READ) return; // shutdown pipe readable, exit if (ev.filter != EVFILT_VNODE) continue; @@ -170,7 +173,8 @@ fn thread_fn(self: *@This(), allocator: std.mem.Allocator) void { return; }; } else if (ev.fflags & NOTE_WRITE != 0) { - self.scan_dir(allocator, dir_path) catch {}; + self.scan_dir(allocator, dir_path) catch |e| + std.log.err("nightwatch: scan_dir failed: {s}", .{@errorName(e)}); } } } @@ -228,10 +232,16 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) } } - const gop = self.snapshots.getOrPut(allocator, dir_path) catch |e| { - return e; - }; - if (!gop.found_existing) gop.value_ptr.* = .empty; + const gop = self.snapshots.getOrPut(allocator, dir_path) catch |e| return e; + if (!gop.found_existing) { + // dir_path points into a stack buffer; dupe it into allocator memory + // so the snapshot key outlives the current thread_fn iteration. + 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; var cit = current_files.iterator(); diff --git a/src/backend/KQueueDir.zig b/src/backend/KQueueDir.zig index 594dd6e..ee8e0b2 100644 --- a/src/backend/KQueueDir.zig +++ b/src/backend/KQueueDir.zig @@ -97,7 +97,10 @@ fn thread_fn(self: *@This(), allocator: std.mem.Allocator) void { var events: [64]std.posix.Kevent = undefined; while (true) { // Block indefinitely until kqueue has events. - const n = std.posix.kevent(self.kq, &.{}, &events, null) catch break; + const n = std.posix.kevent(self.kq, &.{}, &events, null) catch |e| { + std.log.err("nightwatch: kevent failed: {s}, stopping watch thread", .{@errorName(e)}); + break; + }; for (events[0..n]) |ev| { if (ev.filter == EVFILT_READ) return; // shutdown pipe readable, exit if (ev.filter != EVFILT_VNODE) continue; @@ -151,7 +154,8 @@ fn thread_fn(self: *@This(), allocator: std.mem.Allocator) void { return; }; } else if (ev.fflags & NOTE_WRITE != 0) { - self.scan_dir(allocator, watch_path) catch {}; + self.scan_dir(allocator, watch_path) catch |e| + std.log.err("nightwatch: scan_dir failed: {s}", .{@errorName(e)}); } } } @@ -216,10 +220,16 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) } } - const gop = self.snapshots.getOrPut(allocator, dir_path) catch |e| { - return e; - }; - if (!gop.found_existing) gop.value_ptr.* = .empty; + const gop = self.snapshots.getOrPut(allocator, dir_path) catch |e| return e; + if (!gop.found_existing) { + // dir_path points into a stack buffer; dupe it into allocator memory + // so the snapshot key outlives the current thread_fn iteration. + 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; var cit = current_files.iterator(); diff --git a/src/backend/Windows.zig b/src/backend/Windows.zig index a0bc766..89bd94c 100644 --- a/src/backend/Windows.zig +++ b/src/backend/Windows.zig @@ -198,7 +198,8 @@ fn thread_fn( // the `info` pointer (which points into w.buf) a dangling reference. const next_entry_offset = info.NextEntryOffset; watches_mutex.unlock(); - handler.change(full_path, event_type, object_type) catch { + handler.change(full_path, event_type, object_type) catch |e| { + std.log.err("nightwatch: handler returned {s}, stopping watch thread", .{@errorName(e)}); watches_mutex.lock(); break; };