Commit 60955feab8

Andrew Kelley <superjoe30@gmail.com>
2018-08-08 04:12:47
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
1 parent 5cbfe39
Changed files (3)
src-self-hosted
std
src-self-hosted/compilation.zig
@@ -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);
         }
std/event/fs.zig
@@ -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
 }
std/event/loop.zig
@@ -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);