Skip to content

Commit cc671a2

Browse files
committed
std.Build.Watch: implement removing watches for kqueue
1 parent 8819d8b commit cc671a2

File tree

1 file changed

+110
-53
lines changed

1 file changed

+110
-53
lines changed

lib/std/Build/Watch.zig

Lines changed: 110 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,16 @@ const Os = switch (builtin.os.tag) {
605605

606606
kq_fd: i32,
607607
/// Indexes correspond 1:1 with `dir_table`.
608-
reaction_sets: std.ArrayListUnmanaged(ReactionSet),
608+
handles: std.MultiArrayList(struct {
609+
rs: ReactionSet,
610+
/// If the corresponding dir_table Path has sub_path == "", then it
611+
/// suffices as the open directory handle, and this value will be
612+
/// -1. Otherwise, it needs to be opened in update(), and will be
613+
/// stored here.
614+
dir_fd: i32,
615+
/// Number of files being watched by this directory handle.
616+
ref_count: u32,
617+
}),
609618

610619
const dir_open_flags: posix.O = f: {
611620
var f: posix.O = .{
@@ -619,34 +628,39 @@ const Os = switch (builtin.os.tag) {
619628
break :f f;
620629
};
621630

631+
const EV = std.c.EV;
632+
const NOTE = std.c.NOTE;
633+
622634
fn init() !Watch {
623635
const kq_fd = try posix.kqueue();
624636
errdefer posix.close(kq_fd);
625637
return .{
626638
.dir_table = .{},
627639
.os = .{
628640
.kq_fd = kq_fd,
629-
.reaction_sets = .{},
641+
.handles = .empty,
630642
},
631643
.generation = 0,
632644
};
633645
}
634646

635647
fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
648+
const handles = &w.os.handles;
636649
for (steps) |step| {
637650
for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
638651
const reaction_set = rs: {
639652
const gop = try w.dir_table.getOrPut(gpa, path);
640653
if (!gop.found_existing) {
641-
const dir_fd = if (path.sub_path.len == 0)
654+
const skip_open_dir = path.sub_path.len == 0;
655+
const dir_fd = if (skip_open_dir)
642656
path.root_dir.handle.fd
643657
else
644658
posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |err| {
645659
fatal("failed to open directory {}: {s}", .{ path, @errorName(err) });
646660
};
647-
const EV = std.c.EV;
648-
const NOTE = std.c.NOTE;
649-
var changes = [1]posix.Kevent{.{
661+
// Empirically the dir has to stay open or else no events are triggered.
662+
errdefer if (!skip_open_dir) posix.close(dir_fd);
663+
const changes = [1]posix.Kevent{.{
650664
.ident = @bitCast(@as(isize, dir_fd)),
651665
.filter = std.c.EVFILT.VNODE,
652666
.flags = EV.ADD | EV.ENABLE | EV.CLEAR,
@@ -655,12 +669,16 @@ const Os = switch (builtin.os.tag) {
655669
.udata = gop.index,
656670
}};
657671
_ = try posix.kevent(w.os.kq_fd, &changes, &.{}, null);
658-
assert(w.os.reaction_sets.items.len == gop.index);
659-
const reaction_set = try w.os.reaction_sets.addOne(gpa);
660-
reaction_set.* = .{};
661-
break :rs reaction_set;
672+
assert(handles.len == gop.index);
673+
try handles.append(gpa, .{
674+
.rs = .{},
675+
.dir_fd = if (skip_open_dir) -1 else dir_fd,
676+
.ref_count = 1,
677+
});
678+
} else {
679+
handles.items(.ref_count)[gop.index] += 1;
662680
}
663-
break :rs &w.os.reaction_sets.items[gop.index];
681+
break :rs &handles.items(.rs)[gop.index];
664682
};
665683
for (files.items) |basename| {
666684
const gop = try reaction_set.getOrPut(gpa, basename);
@@ -672,47 +690,86 @@ const Os = switch (builtin.os.tag) {
672690

673691
{
674692
// Remove marks for files that are no longer inputs.
675-
//var i: usize = 0;
676-
//while (i < w.os.handle_table.entries.len) {
677-
// {
678-
// const reaction_set = &w.os.handle_table.values()[i];
679-
// var step_set_i: usize = 0;
680-
// while (step_set_i < reaction_set.entries.len) {
681-
// const step_set = &reaction_set.values()[step_set_i];
682-
// var dirent_i: usize = 0;
683-
// while (dirent_i < step_set.entries.len) {
684-
// const generations = step_set.values();
685-
// if (generations[dirent_i] == w.generation) {
686-
// dirent_i += 1;
687-
// continue;
688-
// }
689-
// step_set.swapRemoveAt(dirent_i);
690-
// }
691-
// if (step_set.entries.len > 0) {
692-
// step_set_i += 1;
693-
// continue;
694-
// }
695-
// reaction_set.swapRemoveAt(step_set_i);
696-
// }
697-
// if (reaction_set.entries.len > 0) {
698-
// i += 1;
699-
// continue;
700-
// }
701-
// }
702-
703-
// const path = w.dir_table.keys()[i];
704-
705-
// posix.fanotify_mark(fan_fd, .{
706-
// .REMOVE = true,
707-
// .ONLYDIR = true,
708-
// }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| switch (err) {
709-
// error.FileNotFound => {}, // Expected, harmless.
710-
// else => |e| std.log.warn("unable to unwatch '{}': {s}", .{ path, @errorName(e) }),
711-
// };
712-
713-
// w.dir_table.swapRemoveAt(i);
714-
// w.os.handle_table.swapRemoveAt(i);
715-
//}
693+
var i: usize = 0;
694+
while (i < handles.len) {
695+
{
696+
const reaction_set = &handles.items(.rs)[i];
697+
var step_set_i: usize = 0;
698+
while (step_set_i < reaction_set.entries.len) {
699+
const step_set = &reaction_set.values()[step_set_i];
700+
var dirent_i: usize = 0;
701+
while (dirent_i < step_set.entries.len) {
702+
const generations = step_set.values();
703+
if (generations[dirent_i] == w.generation) {
704+
dirent_i += 1;
705+
continue;
706+
}
707+
step_set.swapRemoveAt(dirent_i);
708+
}
709+
if (step_set.entries.len > 0) {
710+
step_set_i += 1;
711+
continue;
712+
}
713+
reaction_set.swapRemoveAt(step_set_i);
714+
}
715+
if (reaction_set.entries.len > 0) {
716+
i += 1;
717+
continue;
718+
}
719+
}
720+
721+
const ref_count_ptr = &handles.items(.ref_count)[i];
722+
ref_count_ptr.* -= 1;
723+
if (ref_count_ptr.* > 0) continue;
724+
725+
// If the sub_path == "" then this patch has already the
726+
// dir fd that we need to use as the ident to remove the
727+
// event. If it was opened above with openat() then we need
728+
// to access that data via the dir_fd field.
729+
const path = w.dir_table.keys()[i];
730+
const dir_fd = if (path.sub_path.len == 0)
731+
path.root_dir.handle.fd
732+
else
733+
handles.items(.dir_fd)[i];
734+
assert(dir_fd != -1);
735+
736+
// The changelist also needs to update the udata field of the last
737+
// event, since we are doing a swap remove, and we store the dir_table
738+
// index in the udata field.
739+
const last_dir_fd = fd: {
740+
const last_path = w.dir_table.keys()[handles.len - 1];
741+
const last_dir_fd = if (last_path.sub_path.len != 0)
742+
last_path.root_dir.handle.fd
743+
else
744+
handles.items(.dir_fd)[i];
745+
assert(last_dir_fd != -1);
746+
break :fd last_dir_fd;
747+
};
748+
const changes = [_]posix.Kevent{
749+
.{
750+
.ident = @bitCast(@as(isize, dir_fd)),
751+
.filter = std.c.EVFILT.VNODE,
752+
.flags = EV.DELETE,
753+
.fflags = 0,
754+
.data = 0,
755+
.udata = i,
756+
},
757+
.{
758+
.ident = @bitCast(@as(isize, last_dir_fd)),
759+
.filter = std.c.EVFILT.VNODE,
760+
.flags = EV.ADD,
761+
.fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE,
762+
.data = 0,
763+
.udata = i,
764+
},
765+
};
766+
const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes;
767+
_ = try posix.kevent(w.os.kq_fd, filtered_changes, &.{}, null);
768+
if (path.sub_path.len != 0) posix.close(dir_fd);
769+
770+
w.dir_table.swapRemoveAt(i);
771+
handles.swapRemove(i);
772+
}
716773
w.generation +%= 1;
717774
}
718775
}
@@ -722,7 +779,7 @@ const Os = switch (builtin.os.tag) {
722779
var event_buffer: [100]posix.Kevent = undefined;
723780
var n = try posix.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));
724781
if (n == 0) return .timeout;
725-
const reaction_sets = w.os.reaction_sets.items;
782+
const reaction_sets = w.os.handles.items(.rs);
726783
var any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], false);
727784
timespec_buffer = .{ .sec = 0, .nsec = 0 };
728785
while (n == event_buffer.len) {

0 commit comments

Comments
 (0)