std.event.fs.Watch distinguishes between Delete and CloseWrite on darwin

TODO: after 1 event emitted for a deleted file, the file is no longer
watched
This commit is contained in:
Andrew Kelley 2018-08-07 22:12:47 -04:00
parent 5cbfe392be
commit 60955feab8
3 changed files with 93 additions and 48 deletions

View File

@ -758,32 +758,28 @@ pub const Compilation = struct {
// First, get an item from the watch channel, waiting on the channel.
var group = event.Group(BuildError!void).init(self.loop);
{
const ev = await (async self.fs_watch.channel.get() catch unreachable);
const root_scope = switch (ev) {
fs.Watch(*Scope.Root).Event.CloseWrite => |x| x,
fs.Watch(*Scope.Root).Event.Err => |err| {
build_result = err;
continue;
},
const ev = (await (async self.fs_watch.channel.get() catch unreachable)) catch |err| {
build_result = err;
continue;
};
const root_scope = ev.data;
group.call(rebuildFile, self, root_scope) catch |err| {
build_result = err;
continue;
};
}
// Next, get all the items from the channel that are buffered up.
while (await (async self.fs_watch.channel.getOrNull() catch unreachable)) |ev| {
const root_scope = switch (ev) {
fs.Watch(*Scope.Root).Event.CloseWrite => |x| x,
fs.Watch(*Scope.Root).Event.Err => |err| {
while (await (async self.fs_watch.channel.getOrNull() catch unreachable)) |ev_or_err| {
if (ev_or_err) |ev| {
const root_scope = ev.data;
group.call(rebuildFile, self, root_scope) catch |err| {
build_result = err;
continue;
},
};
group.call(rebuildFile, self, root_scope) catch |err| {
};
} else |err| {
build_result = err;
continue;
};
}
}
build_result = await (async group.wait() catch unreachable);
}

View File

@ -358,9 +358,20 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)
}
}
pub const WatchEventId = enum {
CloseWrite,
Delete,
};
pub const WatchEventError = error{
UserResourceLimitReached,
SystemResources,
AccessDenied,
};
pub fn Watch(comptime V: type) type {
return struct {
channel: *event.Channel(Event),
channel: *event.Channel(Event.Error!Event),
os_data: OsData,
const OsData = switch (builtin.os) {
@ -395,19 +406,16 @@ pub fn Watch(comptime V: type) type {
file_table: OsData.FileTable,
};
pub const Event = union(enum) {
CloseWrite: V,
Err: Error,
pub const Event = struct {
id: Id,
data: V,
pub const Error = error{
UserResourceLimitReached,
SystemResources,
AccessDenied,
};
pub const Id = WatchEventId;
pub const Error = WatchEventError;
};
pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self {
const channel = try event.Channel(Self.Event).create(loop, event_buf_count);
const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count);
errdefer channel.destroy();
switch (builtin.os) {
@ -519,19 +527,32 @@ pub fn Watch(comptime V: type) type {
}
while (true) {
(await (async self.channel.loop.bsdWaitKev(
@intCast(usize, close_op.getHandle()), posix.EVFILT_VNODE, posix.NOTE_WRITE,
) catch unreachable)) catch |err| switch (err) {
if (await (async self.channel.loop.bsdWaitKev(
@intCast(usize, close_op.getHandle()),
posix.EVFILT_VNODE,
posix.NOTE_WRITE | posix.NOTE_DELETE,
) catch unreachable)) |kev| {
// TODO handle EV_ERROR
if (kev.fflags & posix.NOTE_DELETE != 0) {
await (async self.channel.put(Self.Event{
.id = Event.Id.Delete,
.data = value_copy,
}) catch unreachable);
} else if (kev.fflags & posix.NOTE_WRITE != 0) {
await (async self.channel.put(Self.Event{
.id = Event.Id.CloseWrite,
.data = value_copy,
}) catch unreachable);
}
} else |err| switch (err) {
error.EventNotFound => unreachable,
error.ProcessNotFound => unreachable,
error.AccessDenied, error.SystemResources => {
// TODO https://github.com/ziglang/zig/issues/769
const casted_err = @errSetCast(error{AccessDenied,SystemResources}, err);
await (async self.channel.put(Self.Event{ .Err = casted_err }) catch unreachable);
await (async self.channel.put(casted_err) catch unreachable);
},
};
await (async self.channel.put(Self.Event{ .CloseWrite = value_copy }) catch unreachable);
}
}
}
@ -582,7 +603,7 @@ pub fn Watch(comptime V: type) type {
@panic("TODO");
}
async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void {
async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void {
// TODO https://github.com/ziglang/zig/issues/1194
suspend {
resume @handle();
@ -743,9 +764,9 @@ async fn testFsWatch(loop: *event.Loop) !void {
}
ev_consumed = true;
switch (await ev) {
Watch(void).Event.CloseWrite => {},
Watch(void).Event.Err => |err| return err,
switch ((try await ev).id) {
WatchEventId.CloseWrite => {},
WatchEventId.Delete => @panic("wrong event"),
}
const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024);
@ -753,4 +774,6 @@ async fn testFsWatch(loop: *event.Loop) !void {
\\line 1
\\lorem ipsum
));
// TODO test deleting the file and then re-adding it. we should get events for both
}

View File

@ -52,6 +52,20 @@ pub const Loop = struct {
base: ResumeNode,
kevent: posix.Kevent,
};
pub const Basic = switch (builtin.os) {
builtin.Os.macosx => struct {
base: ResumeNode,
kev: posix.Kevent,
},
builtin.Os.linux => struct {
base: ResumeNode,
},
builtin.Os.windows => struct {
base: ResumeNode,
},
else => @compileError("unsupported OS"),
};
};
/// After initialization, call run().
@ -379,28 +393,37 @@ pub const Loop = struct {
defer self.linuxRemoveFd(fd);
suspend {
// TODO explicitly put this memory in the coroutine frame #1194
var resume_node = ResumeNode{
.id = ResumeNode.Id.Basic,
.handle = @handle(),
var resume_node = ResumeNode.Basic{
.base = ResumeNode{
.id = ResumeNode.Id.Basic,
.handle = @handle(),
},
};
try self.linuxAddFd(fd, &resume_node, flags);
try self.linuxAddFd(fd, &resume_node.base, flags);
}
}
pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !void {
defer self.bsdRemoveKev(ident, filter);
pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !posix.Kevent {
// TODO #1194
suspend {
// TODO explicitly put this memory in the coroutine frame #1194
var resume_node = ResumeNode{
resume @handle();
}
var resume_node = ResumeNode.Basic{
.base = ResumeNode{
.id = ResumeNode.Id.Basic,
.handle = @handle(),
};
},
.kev = undefined,
};
defer self.bsdRemoveKev(ident, filter);
suspend {
try self.bsdAddKev(&resume_node, ident, filter, fflags);
}
return resume_node.kev;
}
/// resume_node must live longer than the promise that it holds a reference to.
pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode, ident: usize, filter: i16, fflags: u32) !void {
pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode.Basic, ident: usize, filter: i16, fflags: u32) !void {
self.beginOneEvent();
errdefer self.finishOneEvent();
var kev = posix.Kevent{
@ -409,7 +432,7 @@ pub const Loop = struct {
.flags = posix.EV_ADD|posix.EV_ENABLE|posix.EV_CLEAR,
.fflags = fflags,
.data = 0,
.udata = @ptrToInt(resume_node),
.udata = @ptrToInt(&resume_node.base),
};
const kevent_array = (*[1]posix.Kevent)(&kev);
const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
@ -632,7 +655,10 @@ pub const Loop = struct {
const handle = resume_node.handle;
const resume_node_id = resume_node.id;
switch (resume_node_id) {
ResumeNode.Id.Basic => {},
ResumeNode.Id.Basic => {
const basic_node = @fieldParentPtr(ResumeNode.Basic, "base", resume_node);
basic_node.kev = ev;
},
ResumeNode.Id.Stop => return,
ResumeNode.Id.EventFd => {
const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);