fix: log silent errors and fix scan_dir dangling snapshot key

This commit is contained in:
CJ van den Berg 2026-03-15 00:08:07 +01:00
parent 046c304c60
commit eb42d23fc8
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
5 changed files with 43 additions and 14 deletions

View file

@ -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 {};
}

View file

@ -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| {

View file

@ -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();

View file

@ -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();

View file

@ -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;
};