refactor: use polling interface on linux in nightwatch cli

This commit is contained in:
CJ van den Berg 2026-03-09 11:14:47 +01:00
parent 6f8cfc946b
commit dd2bbb66ed
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
4 changed files with 58 additions and 35 deletions

View file

@ -2,6 +2,11 @@ const std = @import("std");
const builtin = @import("builtin");
const nightwatch = @import("nightwatch");
const Watcher = switch (builtin.os.tag) {
.linux => nightwatch.Create(.polling),
else => nightwatch.Default,
};
const is_posix = switch (builtin.os.tag) {
.linux, .macos, .freebsd, .openbsd, .netbsd, .dragonfly => true,
.windows => false,
@ -16,16 +21,23 @@ fn posix_sighandler(_: c_int) callconv(.c) void {
}
const CliHandler = struct {
handler: nightwatch.Handler,
handler: Watcher.Handler,
out: std.fs.File,
ignore: []const []const u8,
const vtable = nightwatch.Handler.VTable{
.change = change_cb,
.rename = rename_cb,
const vtable: Watcher.Handler.VTable = switch (Watcher.interface_type) {
.polling => .{
.change = change_cb,
.rename = rename_cb,
.wait_readable = wait_readable_cb,
},
.threaded => .{
.change = change_cb,
.rename = rename_cb,
},
};
fn change_cb(h: *nightwatch.Handler, path: []const u8, event_type: nightwatch.EventType, object_type: nightwatch.ObjectType) error{HandlerFailed}!void {
fn change_cb(h: *Watcher.Handler, path: []const u8, event_type: nightwatch.EventType, object_type: nightwatch.ObjectType) error{HandlerFailed}!void {
const self: *CliHandler = @fieldParentPtr("handler", h);
for (self.ignore) |ignored| {
if (std.mem.eql(u8, path, ignored)) return;
@ -47,7 +59,7 @@ const CliHandler = struct {
stdout.interface.print("{s} {s} {s}\n", .{ event_label, type_label, path }) catch return error.HandlerFailed;
}
fn rename_cb(h: *nightwatch.Handler, src: []const u8, dst: []const u8, object_type: nightwatch.ObjectType) error{HandlerFailed}!void {
fn rename_cb(h: *Watcher.Handler, src: []const u8, dst: []const u8, object_type: nightwatch.ObjectType) error{HandlerFailed}!void {
const self: *CliHandler = @fieldParentPtr("handler", h);
for (self.ignore) |ignored| {
if (std.mem.eql(u8, src, ignored) or std.mem.eql(u8, dst, ignored)) return;
@ -63,7 +75,7 @@ const CliHandler = struct {
stdout.interface.print("rename {s} {s} -> {s}\n", .{ type_label, src, dst }) catch return error.HandlerFailed;
}
fn wait_readable_cb(_: *nightwatch.Handler) error{HandlerFailed}!nightwatch.ReadableStatus {
fn wait_readable_cb(_: *Watcher.Handler) error{HandlerFailed}!Watcher.Handler.ReadableStatus {
return .will_notify;
}
};
@ -215,7 +227,11 @@ pub fn main() !void {
.ignore = ignore_list.items,
};
var watcher = try nightwatch.Default.init(allocator, &cli_handler.handler);
var watcher = switch (builtin.os.tag) {
.linux => try nightwatch.Create(.polling).init(allocator, &cli_handler.handler),
else => try nightwatch.Default.init(allocator, &cli_handler.handler),
};
defer watcher.deinit();
for (watch_paths.items) |path| {
@ -226,7 +242,7 @@ pub fn main() !void {
try stderr.interface.print("on watch: {s}\n", .{path});
}
if (nightwatch.linux_poll_mode) {
if (nightwatch.Default.interface_type == .polling) {
try run_linux(&watcher);
} else if (builtin.os.tag == .windows) {
run_windows();

View file

@ -6,10 +6,7 @@ const types = @import("types.zig");
pub const EventType = types.EventType;
pub const ObjectType = types.ObjectType;
pub const Error = types.Error;
pub const ReadableStatus = types.ReadableStatus;
pub const InterfaceType = types.InterfaceType;
pub const Handler = types.Handler;
pub const PollingHandler = types.PollingHandler;
pub const Variant = switch (builtin.os.tag) {
.linux => InterfaceType,
@ -49,13 +46,21 @@ pub fn Create(comptime variant: Variant) type {
},
else => @compileError("unsupported OS"),
};
pub const interfaceType: InterfaceType = switch (builtin.os.tag) {
pub const interface_type: InterfaceType = switch (builtin.os.tag) {
.linux => variant,
else => .threaded,
};
pub const Handler = switch (interface_type) {
.threaded => types.Handler,
.polling => types.PollingHandler,
};
pub const InterceptorType = switch (interface_type) {
.threaded => Interceptor,
.polling => PollingInterceptor,
};
allocator: std.mem.Allocator,
interceptor: *Interceptor,
interceptor: *InterceptorType,
/// True if the current backend detects file content modifications in real time.
/// False only when kqueue_dir_only=true, where directory-level watches are used
@ -63,10 +68,10 @@ pub fn Create(comptime variant: Variant) type {
pub const detects_file_modifications = Backend.detects_file_modifications;
pub fn init(allocator: std.mem.Allocator, handler: *Handler) !@This() {
const ic = try allocator.create(Interceptor);
const ic = try allocator.create(InterceptorType);
errdefer allocator.destroy(ic);
ic.* = .{
.handler = .{ .vtable = &Interceptor.vtable },
.handler = .{ .vtable = &InterceptorType.vtable },
.user_handler = handler,
.allocator = allocator,
.backend = undefined,
@ -153,7 +158,7 @@ pub fn Create(comptime variant: Variant) type {
return self.user_handler.rename(src, dst, object_type);
}
fn wait_readable_cb(h: *Handler) error{HandlerFailed}!ReadableStatus {
fn wait_readable_cb(h: *Handler) error{HandlerFailed}!Handler.ReadableStatus {
const self: *Interceptor = @fieldParentPtr("handler", h);
return self.user_handler.wait_readable();
}
@ -171,8 +176,10 @@ pub fn Create(comptime variant: Variant) type {
.wait_readable = wait_readable_cb,
};
fn change_cb(h: *Handler, path: []const u8, event_type: EventType, object_type: ObjectType) error{HandlerFailed}!void {
const self: *Interceptor = @fieldParentPtr("handler", h);
const PollingHandler = types.PollingHandler;
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 {};
recurse_watch(&self.backend, self.allocator, path);
@ -180,13 +187,13 @@ pub fn Create(comptime variant: Variant) type {
return self.user_handler.change(path, event_type, object_type);
}
fn rename_cb(h: *Handler, src: []const u8, dst: []const u8, object_type: ObjectType) error{HandlerFailed}!void {
const self: *Interceptor = @fieldParentPtr("handler", h);
fn rename_cb(h: *PollingHandler, src: []const u8, dst: []const u8, object_type: ObjectType) error{HandlerFailed}!void {
const self: *PollingInterceptor = @fieldParentPtr("handler", h);
return self.user_handler.rename(src, dst, object_type);
}
fn wait_readable_cb(h: *Handler) error{HandlerFailed}!ReadableStatus {
const self: *Interceptor = @fieldParentPtr("handler", h);
fn wait_readable_cb(h: *PollingHandler) error{HandlerFailed}!PollingHandler.ReadableStatus {
const self: *PollingInterceptor = @fieldParentPtr("handler", h);
return self.user_handler.wait_readable();
}
};

View file

@ -22,7 +22,7 @@ const RecordedEvent = union(enum) {
};
const TestHandler = struct {
handler: nw.Handler,
handler: Watcher.Handler,
allocator: std.mem.Allocator,
events: std.ArrayListUnmanaged(RecordedEvent),
@ -46,12 +46,12 @@ const TestHandler = struct {
// vtable
// -----------------------------------------------------------------------
const vtable = nw.Handler.VTable{
const vtable = Watcher.Handler.VTable{
.change = change_cb,
.rename = rename_cb,
};
fn change_cb(handler: *nw.Handler, path: []const u8, event_type: nw.EventType, object_type: nw.ObjectType) error{HandlerFailed}!void {
fn change_cb(handler: *Watcher.Handler, path: []const u8, event_type: nw.EventType, object_type: nw.ObjectType) error{HandlerFailed}!void {
const self: *TestHandler = @fieldParentPtr("handler", handler);
const owned = self.allocator.dupe(u8, path) catch return error.HandlerFailed;
self.events.append(self.allocator, .{
@ -62,7 +62,7 @@ const TestHandler = struct {
};
}
fn rename_cb(handler: *nw.Handler, src: []const u8, dst: []const u8, _: nw.ObjectType) error{HandlerFailed}!void {
fn rename_cb(handler: *Watcher.Handler, src: []const u8, dst: []const u8, _: nw.ObjectType) error{HandlerFailed}!void {
const self: *TestHandler = @fieldParentPtr("handler", handler);
const owned_src = self.allocator.dupe(u8, src) catch return error.HandlerFailed;
errdefer self.allocator.free(owned_src);
@ -78,7 +78,7 @@ const TestHandler = struct {
// On Linux the inotify backend calls wait_readable() inside arm() and
// after each read-drain. We return `will_notify` so it parks; the test
// then calls handle_read_ready() explicitly to drive event delivery.
fn wait_readable_cb(handler: *nw.Handler) error{HandlerFailed}!nw.ReadableStatus {
fn wait_readable_cb(handler: *Watcher.Handler) error{HandlerFailed}!nw.ReadableStatus {
_ = handler;
return .will_notify;
}
@ -180,7 +180,7 @@ fn removeTempDir(path: []const u8) void {
/// - polling watchers: call handle_read_ready() so events are processed.
/// - threaded watchers: the backend uses its own thread/callback; sleep briefly.
fn drainEvents(watcher: *Watcher) !void {
switch (Watcher.interfaceType) {
switch (Watcher.interface_type) {
.polling => try watcher.handle_read_ready(),
.threaded => std.Thread.sleep(300 * std.time.ns_per_ms),
}

View file

@ -56,20 +56,20 @@ pub const PollingHandler = struct {
};
pub const VTable = struct {
change: *const fn (handler: *Handler, path: []const u8, event_type: EventType, object_type: ObjectType) error{HandlerFailed}!void,
rename: *const fn (handler: *Handler, src_path: []const u8, dst_path: []const u8, object_type: ObjectType) error{HandlerFailed}!void,
wait_readable: *const fn (handler: *Handler) error{HandlerFailed}!ReadableStatus,
change: *const fn (handler: *PollingHandler, path: []const u8, event_type: EventType, object_type: ObjectType) error{HandlerFailed}!void,
rename: *const fn (handler: *PollingHandler, src_path: []const u8, dst_path: []const u8, object_type: ObjectType) error{HandlerFailed}!void,
wait_readable: *const fn (handler: *PollingHandler) error{HandlerFailed}!ReadableStatus,
};
pub fn change(handler: *Handler, path: []const u8, event_type: EventType, object_type: ObjectType) error{HandlerFailed}!void {
pub fn change(handler: *PollingHandler, path: []const u8, event_type: EventType, object_type: ObjectType) error{HandlerFailed}!void {
return handler.vtable.change(handler, path, event_type, object_type);
}
pub fn rename(handler: *Handler, src_path: []const u8, dst_path: []const u8, object_type: ObjectType) error{HandlerFailed}!void {
pub fn rename(handler: *PollingHandler, src_path: []const u8, dst_path: []const u8, object_type: ObjectType) error{HandlerFailed}!void {
return handler.vtable.rename(handler, src_path, dst_path, object_type);
}
pub fn wait_readable(handler: *Handler) error{HandlerFailed}!ReadableStatus {
pub fn wait_readable(handler: *PollingHandler) error{HandlerFailed}!ReadableStatus {
return handler.vtable.wait_readable(handler);
}
};