Commit da56959a9a

Andrew Kelley <andrew@ziglang.org>
2019-08-08 22:41:38
closer to std lib event stuff working
1 parent 93840f8
Changed files (3)
std/event/channel.zig
@@ -89,12 +89,7 @@ pub fn Channel(comptime T: type) type {
         /// puts a data item in the channel. The promise completes when the value has been added to the
         /// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter.
         pub async fn put(self: *SelfChannel, data: T) void {
-            // TODO fix this workaround
-            suspend {
-                resume @handle();
-            }
-
-            var my_tick_node = Loop.NextTickNode.init(@handle());
+            var my_tick_node = Loop.NextTickNode.init(@frame());
             var queue_node = std.atomic.Queue(PutNode).Node.init(PutNode{
                 .tick_node = &my_tick_node,
                 .data = data,
@@ -122,15 +117,10 @@ pub fn Channel(comptime T: type) type {
         /// await this function to get an item from the channel. If the buffer is empty, the promise will
         /// complete when the next item is put in the channel.
         pub async fn get(self: *SelfChannel) T {
-            // TODO fix this workaround
-            suspend {
-                resume @handle();
-            }
-
             // TODO integrate this function with named return values
             // so we can get rid of this extra result copy
             var result: T = undefined;
-            var my_tick_node = Loop.NextTickNode.init(@handle());
+            var my_tick_node = Loop.NextTickNode.init(@frame());
             var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
                 .tick_node = &my_tick_node,
                 .data = GetNode.Data{
@@ -173,15 +163,10 @@ pub fn Channel(comptime T: type) type {
         /// Await is necessary for locking purposes. The function will be resumed after checking the channel
         /// for data and will not wait for data to be available.
         pub async fn getOrNull(self: *SelfChannel) ?T {
-            // TODO fix this workaround
-            suspend {
-                resume @handle();
-            }
-
             // TODO integrate this function with named return values
             // so we can get rid of this extra result copy
             var result: ?T = null;
-            var my_tick_node = Loop.NextTickNode.init(@handle());
+            var my_tick_node = Loop.NextTickNode.init(@frame());
             var or_null_node = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node.init(undefined);
             var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
                 .tick_node = &my_tick_node,
@@ -334,41 +319,36 @@ test "std.event.Channel" {
     const channel = try Channel(i32).create(&loop, 0);
     defer channel.destroy();
 
-    const handle = try async<allocator> testChannelGetter(&loop, channel);
-    defer cancel handle;
-
-    const putter = try async<allocator> testChannelPutter(channel);
-    defer cancel putter;
+    const handle = async testChannelGetter(&loop, channel);
+    const putter = async testChannelPutter(channel);
 
     loop.run();
 }
 
 async fn testChannelGetter(loop: *Loop, channel: *Channel(i32)) void {
-    errdefer @panic("test failed");
-
-    const value1_promise = try async channel.get();
+    const value1_promise = async channel.get();
     const value1 = await value1_promise;
     testing.expect(value1 == 1234);
 
-    const value2_promise = try async channel.get();
+    const value2_promise = async channel.get();
     const value2 = await value2_promise;
     testing.expect(value2 == 4567);
 
-    const value3_promise = try async channel.getOrNull();
+    const value3_promise = async channel.getOrNull();
     const value3 = await value3_promise;
     testing.expect(value3 == null);
 
-    const last_put = try async testPut(channel, 4444);
-    const value4 = await try async channel.getOrNull();
+    const last_put = async testPut(channel, 4444);
+    const value4 = channel.getOrNull();
     testing.expect(value4.? == 4444);
     await last_put;
 }
 
 async fn testChannelPutter(channel: *Channel(i32)) void {
-    await (async channel.put(1234) catch @panic("out of memory"));
-    await (async channel.put(4567) catch @panic("out of memory"));
+    channel.put(1234);
+    channel.put(4567);
 }
 
 async fn testPut(channel: *Channel(i32), value: i32) void {
-    await (async channel.put(value) catch @panic("out of memory"));
+    channel.put(value);
 }
std/event/fs.zig
@@ -715,594 +715,594 @@ pub const WatchEventId = enum {
     Delete,
 };
 
-pub const WatchEventError = error{
-    UserResourceLimitReached,
-    SystemResources,
-    AccessDenied,
-    Unexpected, // TODO remove this possibility
-};
-
-pub fn Watch(comptime V: type) type {
-    return struct {
-        channel: *event.Channel(Event.Error!Event),
-        os_data: OsData,
-
-        const OsData = switch (builtin.os) {
-            .macosx, .freebsd, .netbsd => struct {
-                file_table: FileTable,
-                table_lock: event.Lock,
-
-                const FileTable = std.AutoHashMap([]const u8, *Put);
-                const Put = struct {
-                    putter: promise,
-                    value_ptr: *V,
-                };
-            },
-
-            .linux => LinuxOsData,
-            .windows => WindowsOsData,
-
-            else => @compileError("Unsupported OS"),
-        };
-
-        const WindowsOsData = struct {
-            table_lock: event.Lock,
-            dir_table: DirTable,
-            all_putters: std.atomic.Queue(promise),
-            ref_count: std.atomic.Int(usize),
-
-            const DirTable = std.AutoHashMap([]const u8, *Dir);
-            const FileTable = std.AutoHashMap([]const u16, V);
-
-            const Dir = struct {
-                putter: promise,
-                file_table: FileTable,
-                table_lock: event.Lock,
-            };
-        };
-
-        const LinuxOsData = struct {
-            putter: promise,
-            inotify_fd: i32,
-            wd_table: WdTable,
-            table_lock: event.Lock,
-
-            const WdTable = std.AutoHashMap(i32, Dir);
-            const FileTable = std.AutoHashMap([]const u8, V);
-
-            const Dir = struct {
-                dirname: []const u8,
-                file_table: FileTable,
-            };
-        };
-
-        const FileToHandle = std.AutoHashMap([]const u8, promise);
-
-        const Self = @This();
-
-        pub const Event = struct {
-            id: Id,
-            data: V,
-
-            pub const Id = WatchEventId;
-            pub const Error = WatchEventError;
-        };
-
-        pub fn create(loop: *Loop, event_buf_count: usize) !*Self {
-            const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count);
-            errdefer channel.destroy();
-
-            switch (builtin.os) {
-                .linux => {
-                    const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
-                    errdefer os.close(inotify_fd);
-
-                    var result: *Self = undefined;
-                    _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result);
-                    return result;
-                },
-
-                .windows => {
-                    const self = try loop.allocator.create(Self);
-                    errdefer loop.allocator.destroy(self);
-                    self.* = Self{
-                        .channel = channel,
-                        .os_data = OsData{
-                            .table_lock = event.Lock.init(loop),
-                            .dir_table = OsData.DirTable.init(loop.allocator),
-                            .ref_count = std.atomic.Int(usize).init(1),
-                            .all_putters = std.atomic.Queue(promise).init(),
-                        },
-                    };
-                    return self;
-                },
-
-                .macosx, .freebsd, .netbsd => {
-                    const self = try loop.allocator.create(Self);
-                    errdefer loop.allocator.destroy(self);
-
-                    self.* = Self{
-                        .channel = channel,
-                        .os_data = OsData{
-                            .table_lock = event.Lock.init(loop),
-                            .file_table = OsData.FileTable.init(loop.allocator),
-                        },
-                    };
-                    return self;
-                },
-                else => @compileError("Unsupported OS"),
-            }
-        }
-
-        /// All addFile calls and removeFile calls must have completed.
-        pub fn destroy(self: *Self) void {
-            switch (builtin.os) {
-                .macosx, .freebsd, .netbsd => {
-                    // TODO we need to cancel the coroutines before destroying the lock
-                    self.os_data.table_lock.deinit();
-                    var it = self.os_data.file_table.iterator();
-                    while (it.next()) |entry| {
-                        cancel entry.value.putter;
-                        self.channel.loop.allocator.free(entry.key);
-                    }
-                    self.channel.destroy();
-                },
-                .linux => cancel self.os_data.putter,
-                .windows => {
-                    while (self.os_data.all_putters.get()) |putter_node| {
-                        cancel putter_node.data;
-                    }
-                    self.deref();
-                },
-                else => @compileError("Unsupported OS"),
-            }
-        }
-
-        fn ref(self: *Self) void {
-            _ = self.os_data.ref_count.incr();
-        }
-
-        fn deref(self: *Self) void {
-            if (self.os_data.ref_count.decr() == 1) {
-                const allocator = self.channel.loop.allocator;
-                self.os_data.table_lock.deinit();
-                var it = self.os_data.dir_table.iterator();
-                while (it.next()) |entry| {
-                    allocator.free(entry.key);
-                    allocator.destroy(entry.value);
-                }
-                self.os_data.dir_table.deinit();
-                self.channel.destroy();
-                allocator.destroy(self);
-            }
-        }
-
-        pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
-            switch (builtin.os) {
-                .macosx, .freebsd, .netbsd => return await (async addFileKEvent(self, file_path, value) catch unreachable),
-                .linux => return await (async addFileLinux(self, file_path, value) catch unreachable),
-                .windows => return await (async addFileWindows(self, file_path, value) catch unreachable),
-                else => @compileError("Unsupported OS"),
-            }
-        }
-
-        async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
-            const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [_][]const u8{file_path});
-            var resolved_path_consumed = false;
-            defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path);
-
-            var close_op = try CloseOperation.start(self.channel.loop);
-            var close_op_consumed = false;
-            defer if (!close_op_consumed) close_op.finish();
-
-            const flags = if (os.darwin.is_the_target) os.O_SYMLINK | os.O_EVTONLY else 0;
-            const mode = 0;
-            const fd = try await (async openPosix(self.channel.loop, resolved_path, flags, mode) catch unreachable);
-            close_op.setHandle(fd);
-
-            var put_data: *OsData.Put = undefined;
-            const putter = try async self.kqPutEvents(close_op, value, &put_data);
-            close_op_consumed = true;
-            errdefer cancel putter;
-
-            const result = blk: {
-                const held = await (async self.os_data.table_lock.acquire() catch unreachable);
-                defer held.release();
-
-                const gop = try self.os_data.file_table.getOrPut(resolved_path);
-                if (gop.found_existing) {
-                    const prev_value = gop.kv.value.value_ptr.*;
-                    cancel gop.kv.value.putter;
-                    gop.kv.value = put_data;
-                    break :blk prev_value;
-                } else {
-                    resolved_path_consumed = true;
-                    gop.kv.value = put_data;
-                    break :blk null;
-                }
-            };
-
-            return result;
-        }
-
-        async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void {
-            // TODO https://github.com/ziglang/zig/issues/1194
-            suspend {
-                resume @handle();
-            }
-
-            var value_copy = value;
-            var put = OsData.Put{
-                .putter = @handle(),
-                .value_ptr = &value_copy,
-            };
-            out_put.* = &put;
-            self.channel.loop.beginOneEvent();
-
-            defer {
-                close_op.finish();
-                self.channel.loop.finishOneEvent();
-            }
-
-            while (true) {
-                if (await (async self.channel.loop.bsdWaitKev(
-                    @intCast(usize, close_op.getHandle()),
-                    os.EVFILT_VNODE,
-                    os.NOTE_WRITE | os.NOTE_DELETE,
-                ) catch unreachable)) |kev| {
-                    // TODO handle EV_ERROR
-                    if (kev.fflags & os.NOTE_DELETE != 0) {
-                        await (async self.channel.put(Self.Event{
-                            .id = Event.Id.Delete,
-                            .data = value_copy,
-                        }) catch unreachable);
-                    } else if (kev.fflags & os.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.Overflow => unreachable,
-                    error.AccessDenied, error.SystemResources => |casted_err| {
-                        await (async self.channel.put(casted_err) catch unreachable);
-                    },
-                }
-            }
-        }
-
-        async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
-            const value_copy = value;
-
-            const dirname = std.fs.path.dirname(file_path) orelse ".";
-            const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
-            var dirname_with_null_consumed = false;
-            defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null);
-
-            const basename = std.fs.path.basename(file_path);
-            const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename);
-            var basename_with_null_consumed = false;
-            defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
-
-            const wd = try os.inotify_add_watchC(
-                self.os_data.inotify_fd,
-                dirname_with_null.ptr,
-                os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
-            );
-            // wd is either a newly created watch or an existing one.
-
-            const held = await (async self.os_data.table_lock.acquire() catch unreachable);
-            defer held.release();
-
-            const gop = try self.os_data.wd_table.getOrPut(wd);
-            if (!gop.found_existing) {
-                gop.kv.value = OsData.Dir{
-                    .dirname = dirname_with_null,
-                    .file_table = OsData.FileTable.init(self.channel.loop.allocator),
-                };
-                dirname_with_null_consumed = true;
-            }
-            const dir = &gop.kv.value;
-
-            const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
-            if (file_table_gop.found_existing) {
-                const prev_value = file_table_gop.kv.value;
-                file_table_gop.kv.value = value_copy;
-                return prev_value;
-            } else {
-                file_table_gop.kv.value = value_copy;
-                basename_with_null_consumed = true;
-                return null;
-            }
-        }
-
-        async fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
-            const value_copy = value;
-            // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
-
-            const dirname = try std.mem.dupe(self.channel.loop.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
-            var dirname_consumed = false;
-            defer if (!dirname_consumed) self.channel.loop.allocator.free(dirname);
-
-            const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, dirname);
-            defer self.channel.loop.allocator.free(dirname_utf16le);
-
-            // TODO https://github.com/ziglang/zig/issues/265
-            const basename = std.fs.path.basename(file_path);
-            const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
-            var basename_utf16le_null_consumed = false;
-            defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
-            const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
-
-            const dir_handle = try windows.CreateFileW(
-                dirname_utf16le.ptr,
-                windows.FILE_LIST_DIRECTORY,
-                windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
-                null,
-                windows.OPEN_EXISTING,
-                windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
-                null,
-            );
-            var dir_handle_consumed = false;
-            defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
-
-            const held = await (async self.os_data.table_lock.acquire() catch unreachable);
-            defer held.release();
-
-            const gop = try self.os_data.dir_table.getOrPut(dirname);
-            if (gop.found_existing) {
-                const dir = gop.kv.value;
-                const held_dir_lock = await (async dir.table_lock.acquire() catch unreachable);
-                defer held_dir_lock.release();
-
-                const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
-                if (file_gop.found_existing) {
-                    const prev_value = file_gop.kv.value;
-                    file_gop.kv.value = value_copy;
-                    return prev_value;
-                } else {
-                    file_gop.kv.value = value_copy;
-                    basename_utf16le_null_consumed = true;
-                    return null;
-                }
-            } else {
-                errdefer _ = self.os_data.dir_table.remove(dirname);
-                const dir = try self.channel.loop.allocator.create(OsData.Dir);
-                errdefer self.channel.loop.allocator.destroy(dir);
-
-                dir.* = OsData.Dir{
-                    .file_table = OsData.FileTable.init(self.channel.loop.allocator),
-                    .table_lock = event.Lock.init(self.channel.loop),
-                    .putter = undefined,
-                };
-                gop.kv.value = dir;
-                assert((try dir.file_table.put(basename_utf16le_no_null, value_copy)) == null);
-                basename_utf16le_null_consumed = true;
-
-                dir.putter = try async self.windowsDirReader(dir_handle, dir);
-                dir_handle_consumed = true;
-
-                dirname_consumed = true;
-
-                return null;
-            }
-        }
-
-        async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
-            // TODO https://github.com/ziglang/zig/issues/1194
-            suspend {
-                resume @handle();
-            }
-
-            self.ref();
-            defer self.deref();
-
-            defer os.close(dir_handle);
-
-            var putter_node = std.atomic.Queue(promise).Node{
-                .data = @handle(),
-                .prev = null,
-                .next = null,
-            };
-            self.os_data.all_putters.put(&putter_node);
-            defer _ = self.os_data.all_putters.remove(&putter_node);
-
-            var resume_node = Loop.ResumeNode.Basic{
-                .base = Loop.ResumeNode{
-                    .id = Loop.ResumeNode.Id.Basic,
-                    .handle = @handle(),
-                    .overlapped = windows.OVERLAPPED{
-                        .Internal = 0,
-                        .InternalHigh = 0,
-                        .Offset = 0,
-                        .OffsetHigh = 0,
-                        .hEvent = null,
-                    },
-                },
-            };
-            var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
-
-            // TODO handle this error not in the channel but in the setup
-            _ = windows.CreateIoCompletionPort(
-                dir_handle,
-                self.channel.loop.os_data.io_port,
-                undefined,
-                undefined,
-            ) catch |err| {
-                await (async self.channel.put(err) catch unreachable);
-                return;
-            };
-
-            while (true) {
-                {
-                    // TODO only 1 beginOneEvent for the whole coroutine
-                    self.channel.loop.beginOneEvent();
-                    errdefer self.channel.loop.finishOneEvent();
-                    errdefer {
-                        _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
-                    }
-                    suspend {
-                        _ = windows.kernel32.ReadDirectoryChangesW(
-                            dir_handle,
-                            &event_buf,
-                            @intCast(windows.DWORD, event_buf.len),
-                            windows.FALSE, // watch subtree
-                            windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
-                                windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
-                                windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
-                                windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
-                            null, // number of bytes transferred (unused for async)
-                            &resume_node.base.overlapped,
-                            null, // completion routine - unused because we use IOCP
-                        );
-                    }
-                }
-                var bytes_transferred: windows.DWORD = undefined;
-                if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
-                    const err = switch (windows.kernel32.GetLastError()) {
-                        else => |err| windows.unexpectedError(err),
-                    };
-                    await (async self.channel.put(err) catch unreachable);
-                } else {
-                    // can't use @bytesToSlice because of the special variable length name field
-                    var ptr = event_buf[0..].ptr;
-                    const end_ptr = ptr + bytes_transferred;
-                    var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
-                    while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
-                        ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
-                        const emit = switch (ev.Action) {
-                            windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
-                            windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
-                            else => null,
-                        };
-                        if (emit) |id| {
-                            const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
-                            const user_value = blk: {
-                                const held = await (async dir.table_lock.acquire() catch unreachable);
-                                defer held.release();
-
-                                if (dir.file_table.get(basename_utf16le)) |entry| {
-                                    break :blk entry.value;
-                                } else {
-                                    break :blk null;
-                                }
-                            };
-                            if (user_value) |v| {
-                                await (async self.channel.put(Event{
-                                    .id = id,
-                                    .data = v,
-                                }) catch unreachable);
-                            }
-                        }
-                        if (ev.NextEntryOffset == 0) break;
-                    }
-                }
-            }
-        }
-
-        pub async fn removeFile(self: *Self, file_path: []const u8) ?V {
-            @panic("TODO");
-        }
-
-        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();
-            }
-
-            const loop = channel.loop;
-
-            var watch = Self{
-                .channel = channel,
-                .os_data = OsData{
-                    .putter = @handle(),
-                    .inotify_fd = inotify_fd,
-                    .wd_table = OsData.WdTable.init(loop.allocator),
-                    .table_lock = event.Lock.init(loop),
-                },
-            };
-            out_watch.* = &watch;
-
-            loop.beginOneEvent();
-
-            defer {
-                watch.os_data.table_lock.deinit();
-                var wd_it = watch.os_data.wd_table.iterator();
-                while (wd_it.next()) |wd_entry| {
-                    var file_it = wd_entry.value.file_table.iterator();
-                    while (file_it.next()) |file_entry| {
-                        loop.allocator.free(file_entry.key);
-                    }
-                    loop.allocator.free(wd_entry.value.dirname);
-                }
-                loop.finishOneEvent();
-                os.close(inotify_fd);
-                channel.destroy();
-            }
-
-            var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
-
-            while (true) {
-                const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
-                const errno = os.linux.getErrno(rc);
-                switch (errno) {
-                    0 => {
-                        // can't use @bytesToSlice because of the special variable length name field
-                        var ptr = event_buf[0..].ptr;
-                        const end_ptr = ptr + event_buf.len;
-                        var ev: *os.linux.inotify_event = undefined;
-                        while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
-                            ev = @ptrCast(*os.linux.inotify_event, ptr);
-                            if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
-                                const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
-                                const basename_with_null = basename_ptr[0 .. std.mem.len(u8, basename_ptr) + 1];
-                                const user_value = blk: {
-                                    const held = await (async watch.os_data.table_lock.acquire() catch unreachable);
-                                    defer held.release();
-
-                                    const dir = &watch.os_data.wd_table.get(ev.wd).?.value;
-                                    if (dir.file_table.get(basename_with_null)) |entry| {
-                                        break :blk entry.value;
-                                    } else {
-                                        break :blk null;
-                                    }
-                                };
-                                if (user_value) |v| {
-                                    await (async channel.put(Event{
-                                        .id = WatchEventId.CloseWrite,
-                                        .data = v,
-                                    }) catch unreachable);
-                                }
-                            }
-                        }
-                    },
-                    os.linux.EINTR => continue,
-                    os.linux.EINVAL => unreachable,
-                    os.linux.EFAULT => unreachable,
-                    os.linux.EAGAIN => {
-                        (await (async loop.linuxWaitFd(
-                            inotify_fd,
-                            os.linux.EPOLLET | os.linux.EPOLLIN,
-                        ) catch unreachable)) catch |err| {
-                            const transformed_err = switch (err) {
-                                error.FileDescriptorAlreadyPresentInSet => unreachable,
-                                error.OperationCausesCircularLoop => unreachable,
-                                error.FileDescriptorNotRegistered => unreachable,
-                                error.FileDescriptorIncompatibleWithEpoll => unreachable,
-                                error.Unexpected => unreachable,
-                                else => |e| e,
-                            };
-                            await (async channel.put(transformed_err) catch unreachable);
-                        };
-                    },
-                    else => unreachable,
-                }
-            }
-        }
-    };
-}
+//pub const WatchEventError = error{
+//    UserResourceLimitReached,
+//    SystemResources,
+//    AccessDenied,
+//    Unexpected, // TODO remove this possibility
+//};
+//
+//pub fn Watch(comptime V: type) type {
+//    return struct {
+//        channel: *event.Channel(Event.Error!Event),
+//        os_data: OsData,
+//
+//        const OsData = switch (builtin.os) {
+//            .macosx, .freebsd, .netbsd => struct {
+//                file_table: FileTable,
+//                table_lock: event.Lock,
+//
+//                const FileTable = std.AutoHashMap([]const u8, *Put);
+//                const Put = struct {
+//                    putter: promise,
+//                    value_ptr: *V,
+//                };
+//            },
+//
+//            .linux => LinuxOsData,
+//            .windows => WindowsOsData,
+//
+//            else => @compileError("Unsupported OS"),
+//        };
+//
+//        const WindowsOsData = struct {
+//            table_lock: event.Lock,
+//            dir_table: DirTable,
+//            all_putters: std.atomic.Queue(promise),
+//            ref_count: std.atomic.Int(usize),
+//
+//            const DirTable = std.AutoHashMap([]const u8, *Dir);
+//            const FileTable = std.AutoHashMap([]const u16, V);
+//
+//            const Dir = struct {
+//                putter: promise,
+//                file_table: FileTable,
+//                table_lock: event.Lock,
+//            };
+//        };
+//
+//        const LinuxOsData = struct {
+//            putter: promise,
+//            inotify_fd: i32,
+//            wd_table: WdTable,
+//            table_lock: event.Lock,
+//
+//            const WdTable = std.AutoHashMap(i32, Dir);
+//            const FileTable = std.AutoHashMap([]const u8, V);
+//
+//            const Dir = struct {
+//                dirname: []const u8,
+//                file_table: FileTable,
+//            };
+//        };
+//
+//        const FileToHandle = std.AutoHashMap([]const u8, promise);
+//
+//        const Self = @This();
+//
+//        pub const Event = struct {
+//            id: Id,
+//            data: V,
+//
+//            pub const Id = WatchEventId;
+//            pub const Error = WatchEventError;
+//        };
+//
+//        pub fn create(loop: *Loop, event_buf_count: usize) !*Self {
+//            const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count);
+//            errdefer channel.destroy();
+//
+//            switch (builtin.os) {
+//                .linux => {
+//                    const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
+//                    errdefer os.close(inotify_fd);
+//
+//                    var result: *Self = undefined;
+//                    _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result);
+//                    return result;
+//                },
+//
+//                .windows => {
+//                    const self = try loop.allocator.create(Self);
+//                    errdefer loop.allocator.destroy(self);
+//                    self.* = Self{
+//                        .channel = channel,
+//                        .os_data = OsData{
+//                            .table_lock = event.Lock.init(loop),
+//                            .dir_table = OsData.DirTable.init(loop.allocator),
+//                            .ref_count = std.atomic.Int(usize).init(1),
+//                            .all_putters = std.atomic.Queue(promise).init(),
+//                        },
+//                    };
+//                    return self;
+//                },
+//
+//                .macosx, .freebsd, .netbsd => {
+//                    const self = try loop.allocator.create(Self);
+//                    errdefer loop.allocator.destroy(self);
+//
+//                    self.* = Self{
+//                        .channel = channel,
+//                        .os_data = OsData{
+//                            .table_lock = event.Lock.init(loop),
+//                            .file_table = OsData.FileTable.init(loop.allocator),
+//                        },
+//                    };
+//                    return self;
+//                },
+//                else => @compileError("Unsupported OS"),
+//            }
+//        }
+//
+//        /// All addFile calls and removeFile calls must have completed.
+//        pub fn destroy(self: *Self) void {
+//            switch (builtin.os) {
+//                .macosx, .freebsd, .netbsd => {
+//                    // TODO we need to cancel the coroutines before destroying the lock
+//                    self.os_data.table_lock.deinit();
+//                    var it = self.os_data.file_table.iterator();
+//                    while (it.next()) |entry| {
+//                        cancel entry.value.putter;
+//                        self.channel.loop.allocator.free(entry.key);
+//                    }
+//                    self.channel.destroy();
+//                },
+//                .linux => cancel self.os_data.putter,
+//                .windows => {
+//                    while (self.os_data.all_putters.get()) |putter_node| {
+//                        cancel putter_node.data;
+//                    }
+//                    self.deref();
+//                },
+//                else => @compileError("Unsupported OS"),
+//            }
+//        }
+//
+//        fn ref(self: *Self) void {
+//            _ = self.os_data.ref_count.incr();
+//        }
+//
+//        fn deref(self: *Self) void {
+//            if (self.os_data.ref_count.decr() == 1) {
+//                const allocator = self.channel.loop.allocator;
+//                self.os_data.table_lock.deinit();
+//                var it = self.os_data.dir_table.iterator();
+//                while (it.next()) |entry| {
+//                    allocator.free(entry.key);
+//                    allocator.destroy(entry.value);
+//                }
+//                self.os_data.dir_table.deinit();
+//                self.channel.destroy();
+//                allocator.destroy(self);
+//            }
+//        }
+//
+//        pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
+//            switch (builtin.os) {
+//                .macosx, .freebsd, .netbsd => return await (async addFileKEvent(self, file_path, value) catch unreachable),
+//                .linux => return await (async addFileLinux(self, file_path, value) catch unreachable),
+//                .windows => return await (async addFileWindows(self, file_path, value) catch unreachable),
+//                else => @compileError("Unsupported OS"),
+//            }
+//        }
+//
+//        async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
+//            const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [_][]const u8{file_path});
+//            var resolved_path_consumed = false;
+//            defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path);
+//
+//            var close_op = try CloseOperation.start(self.channel.loop);
+//            var close_op_consumed = false;
+//            defer if (!close_op_consumed) close_op.finish();
+//
+//            const flags = if (os.darwin.is_the_target) os.O_SYMLINK | os.O_EVTONLY else 0;
+//            const mode = 0;
+//            const fd = try await (async openPosix(self.channel.loop, resolved_path, flags, mode) catch unreachable);
+//            close_op.setHandle(fd);
+//
+//            var put_data: *OsData.Put = undefined;
+//            const putter = try async self.kqPutEvents(close_op, value, &put_data);
+//            close_op_consumed = true;
+//            errdefer cancel putter;
+//
+//            const result = blk: {
+//                const held = await (async self.os_data.table_lock.acquire() catch unreachable);
+//                defer held.release();
+//
+//                const gop = try self.os_data.file_table.getOrPut(resolved_path);
+//                if (gop.found_existing) {
+//                    const prev_value = gop.kv.value.value_ptr.*;
+//                    cancel gop.kv.value.putter;
+//                    gop.kv.value = put_data;
+//                    break :blk prev_value;
+//                } else {
+//                    resolved_path_consumed = true;
+//                    gop.kv.value = put_data;
+//                    break :blk null;
+//                }
+//            };
+//
+//            return result;
+//        }
+//
+//        async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void {
+//            // TODO https://github.com/ziglang/zig/issues/1194
+//            suspend {
+//                resume @handle();
+//            }
+//
+//            var value_copy = value;
+//            var put = OsData.Put{
+//                .putter = @handle(),
+//                .value_ptr = &value_copy,
+//            };
+//            out_put.* = &put;
+//            self.channel.loop.beginOneEvent();
+//
+//            defer {
+//                close_op.finish();
+//                self.channel.loop.finishOneEvent();
+//            }
+//
+//            while (true) {
+//                if (await (async self.channel.loop.bsdWaitKev(
+//                    @intCast(usize, close_op.getHandle()),
+//                    os.EVFILT_VNODE,
+//                    os.NOTE_WRITE | os.NOTE_DELETE,
+//                ) catch unreachable)) |kev| {
+//                    // TODO handle EV_ERROR
+//                    if (kev.fflags & os.NOTE_DELETE != 0) {
+//                        await (async self.channel.put(Self.Event{
+//                            .id = Event.Id.Delete,
+//                            .data = value_copy,
+//                        }) catch unreachable);
+//                    } else if (kev.fflags & os.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.Overflow => unreachable,
+//                    error.AccessDenied, error.SystemResources => |casted_err| {
+//                        await (async self.channel.put(casted_err) catch unreachable);
+//                    },
+//                }
+//            }
+//        }
+//
+//        async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
+//            const value_copy = value;
+//
+//            const dirname = std.fs.path.dirname(file_path) orelse ".";
+//            const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
+//            var dirname_with_null_consumed = false;
+//            defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null);
+//
+//            const basename = std.fs.path.basename(file_path);
+//            const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename);
+//            var basename_with_null_consumed = false;
+//            defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
+//
+//            const wd = try os.inotify_add_watchC(
+//                self.os_data.inotify_fd,
+//                dirname_with_null.ptr,
+//                os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
+//            );
+//            // wd is either a newly created watch or an existing one.
+//
+//            const held = await (async self.os_data.table_lock.acquire() catch unreachable);
+//            defer held.release();
+//
+//            const gop = try self.os_data.wd_table.getOrPut(wd);
+//            if (!gop.found_existing) {
+//                gop.kv.value = OsData.Dir{
+//                    .dirname = dirname_with_null,
+//                    .file_table = OsData.FileTable.init(self.channel.loop.allocator),
+//                };
+//                dirname_with_null_consumed = true;
+//            }
+//            const dir = &gop.kv.value;
+//
+//            const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
+//            if (file_table_gop.found_existing) {
+//                const prev_value = file_table_gop.kv.value;
+//                file_table_gop.kv.value = value_copy;
+//                return prev_value;
+//            } else {
+//                file_table_gop.kv.value = value_copy;
+//                basename_with_null_consumed = true;
+//                return null;
+//            }
+//        }
+//
+//        async fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
+//            const value_copy = value;
+//            // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
+//
+//            const dirname = try std.mem.dupe(self.channel.loop.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
+//            var dirname_consumed = false;
+//            defer if (!dirname_consumed) self.channel.loop.allocator.free(dirname);
+//
+//            const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, dirname);
+//            defer self.channel.loop.allocator.free(dirname_utf16le);
+//
+//            // TODO https://github.com/ziglang/zig/issues/265
+//            const basename = std.fs.path.basename(file_path);
+//            const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
+//            var basename_utf16le_null_consumed = false;
+//            defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
+//            const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
+//
+//            const dir_handle = try windows.CreateFileW(
+//                dirname_utf16le.ptr,
+//                windows.FILE_LIST_DIRECTORY,
+//                windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
+//                null,
+//                windows.OPEN_EXISTING,
+//                windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
+//                null,
+//            );
+//            var dir_handle_consumed = false;
+//            defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
+//
+//            const held = await (async self.os_data.table_lock.acquire() catch unreachable);
+//            defer held.release();
+//
+//            const gop = try self.os_data.dir_table.getOrPut(dirname);
+//            if (gop.found_existing) {
+//                const dir = gop.kv.value;
+//                const held_dir_lock = await (async dir.table_lock.acquire() catch unreachable);
+//                defer held_dir_lock.release();
+//
+//                const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
+//                if (file_gop.found_existing) {
+//                    const prev_value = file_gop.kv.value;
+//                    file_gop.kv.value = value_copy;
+//                    return prev_value;
+//                } else {
+//                    file_gop.kv.value = value_copy;
+//                    basename_utf16le_null_consumed = true;
+//                    return null;
+//                }
+//            } else {
+//                errdefer _ = self.os_data.dir_table.remove(dirname);
+//                const dir = try self.channel.loop.allocator.create(OsData.Dir);
+//                errdefer self.channel.loop.allocator.destroy(dir);
+//
+//                dir.* = OsData.Dir{
+//                    .file_table = OsData.FileTable.init(self.channel.loop.allocator),
+//                    .table_lock = event.Lock.init(self.channel.loop),
+//                    .putter = undefined,
+//                };
+//                gop.kv.value = dir;
+//                assert((try dir.file_table.put(basename_utf16le_no_null, value_copy)) == null);
+//                basename_utf16le_null_consumed = true;
+//
+//                dir.putter = try async self.windowsDirReader(dir_handle, dir);
+//                dir_handle_consumed = true;
+//
+//                dirname_consumed = true;
+//
+//                return null;
+//            }
+//        }
+//
+//        async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
+//            // TODO https://github.com/ziglang/zig/issues/1194
+//            suspend {
+//                resume @handle();
+//            }
+//
+//            self.ref();
+//            defer self.deref();
+//
+//            defer os.close(dir_handle);
+//
+//            var putter_node = std.atomic.Queue(promise).Node{
+//                .data = @handle(),
+//                .prev = null,
+//                .next = null,
+//            };
+//            self.os_data.all_putters.put(&putter_node);
+//            defer _ = self.os_data.all_putters.remove(&putter_node);
+//
+//            var resume_node = Loop.ResumeNode.Basic{
+//                .base = Loop.ResumeNode{
+//                    .id = Loop.ResumeNode.Id.Basic,
+//                    .handle = @handle(),
+//                    .overlapped = windows.OVERLAPPED{
+//                        .Internal = 0,
+//                        .InternalHigh = 0,
+//                        .Offset = 0,
+//                        .OffsetHigh = 0,
+//                        .hEvent = null,
+//                    },
+//                },
+//            };
+//            var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
+//
+//            // TODO handle this error not in the channel but in the setup
+//            _ = windows.CreateIoCompletionPort(
+//                dir_handle,
+//                self.channel.loop.os_data.io_port,
+//                undefined,
+//                undefined,
+//            ) catch |err| {
+//                await (async self.channel.put(err) catch unreachable);
+//                return;
+//            };
+//
+//            while (true) {
+//                {
+//                    // TODO only 1 beginOneEvent for the whole coroutine
+//                    self.channel.loop.beginOneEvent();
+//                    errdefer self.channel.loop.finishOneEvent();
+//                    errdefer {
+//                        _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
+//                    }
+//                    suspend {
+//                        _ = windows.kernel32.ReadDirectoryChangesW(
+//                            dir_handle,
+//                            &event_buf,
+//                            @intCast(windows.DWORD, event_buf.len),
+//                            windows.FALSE, // watch subtree
+//                            windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
+//                                windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
+//                                windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
+//                                windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
+//                            null, // number of bytes transferred (unused for async)
+//                            &resume_node.base.overlapped,
+//                            null, // completion routine - unused because we use IOCP
+//                        );
+//                    }
+//                }
+//                var bytes_transferred: windows.DWORD = undefined;
+//                if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
+//                    const err = switch (windows.kernel32.GetLastError()) {
+//                        else => |err| windows.unexpectedError(err),
+//                    };
+//                    await (async self.channel.put(err) catch unreachable);
+//                } else {
+//                    // can't use @bytesToSlice because of the special variable length name field
+//                    var ptr = event_buf[0..].ptr;
+//                    const end_ptr = ptr + bytes_transferred;
+//                    var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
+//                    while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
+//                        ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
+//                        const emit = switch (ev.Action) {
+//                            windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
+//                            windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
+//                            else => null,
+//                        };
+//                        if (emit) |id| {
+//                            const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
+//                            const user_value = blk: {
+//                                const held = await (async dir.table_lock.acquire() catch unreachable);
+//                                defer held.release();
+//
+//                                if (dir.file_table.get(basename_utf16le)) |entry| {
+//                                    break :blk entry.value;
+//                                } else {
+//                                    break :blk null;
+//                                }
+//                            };
+//                            if (user_value) |v| {
+//                                await (async self.channel.put(Event{
+//                                    .id = id,
+//                                    .data = v,
+//                                }) catch unreachable);
+//                            }
+//                        }
+//                        if (ev.NextEntryOffset == 0) break;
+//                    }
+//                }
+//            }
+//        }
+//
+//        pub async fn removeFile(self: *Self, file_path: []const u8) ?V {
+//            @panic("TODO");
+//        }
+//
+//        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();
+//            }
+//
+//            const loop = channel.loop;
+//
+//            var watch = Self{
+//                .channel = channel,
+//                .os_data = OsData{
+//                    .putter = @handle(),
+//                    .inotify_fd = inotify_fd,
+//                    .wd_table = OsData.WdTable.init(loop.allocator),
+//                    .table_lock = event.Lock.init(loop),
+//                },
+//            };
+//            out_watch.* = &watch;
+//
+//            loop.beginOneEvent();
+//
+//            defer {
+//                watch.os_data.table_lock.deinit();
+//                var wd_it = watch.os_data.wd_table.iterator();
+//                while (wd_it.next()) |wd_entry| {
+//                    var file_it = wd_entry.value.file_table.iterator();
+//                    while (file_it.next()) |file_entry| {
+//                        loop.allocator.free(file_entry.key);
+//                    }
+//                    loop.allocator.free(wd_entry.value.dirname);
+//                }
+//                loop.finishOneEvent();
+//                os.close(inotify_fd);
+//                channel.destroy();
+//            }
+//
+//            var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
+//
+//            while (true) {
+//                const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
+//                const errno = os.linux.getErrno(rc);
+//                switch (errno) {
+//                    0 => {
+//                        // can't use @bytesToSlice because of the special variable length name field
+//                        var ptr = event_buf[0..].ptr;
+//                        const end_ptr = ptr + event_buf.len;
+//                        var ev: *os.linux.inotify_event = undefined;
+//                        while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
+//                            ev = @ptrCast(*os.linux.inotify_event, ptr);
+//                            if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
+//                                const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
+//                                const basename_with_null = basename_ptr[0 .. std.mem.len(u8, basename_ptr) + 1];
+//                                const user_value = blk: {
+//                                    const held = await (async watch.os_data.table_lock.acquire() catch unreachable);
+//                                    defer held.release();
+//
+//                                    const dir = &watch.os_data.wd_table.get(ev.wd).?.value;
+//                                    if (dir.file_table.get(basename_with_null)) |entry| {
+//                                        break :blk entry.value;
+//                                    } else {
+//                                        break :blk null;
+//                                    }
+//                                };
+//                                if (user_value) |v| {
+//                                    await (async channel.put(Event{
+//                                        .id = WatchEventId.CloseWrite,
+//                                        .data = v,
+//                                    }) catch unreachable);
+//                                }
+//                            }
+//                        }
+//                    },
+//                    os.linux.EINTR => continue,
+//                    os.linux.EINVAL => unreachable,
+//                    os.linux.EFAULT => unreachable,
+//                    os.linux.EAGAIN => {
+//                        (await (async loop.linuxWaitFd(
+//                            inotify_fd,
+//                            os.linux.EPOLLET | os.linux.EPOLLIN,
+//                        ) catch unreachable)) catch |err| {
+//                            const transformed_err = switch (err) {
+//                                error.FileDescriptorAlreadyPresentInSet => unreachable,
+//                                error.OperationCausesCircularLoop => unreachable,
+//                                error.FileDescriptorNotRegistered => unreachable,
+//                                error.FileDescriptorIncompatibleWithEpoll => unreachable,
+//                                error.Unexpected => unreachable,
+//                                else => |e| e,
+//                            };
+//                            await (async channel.put(transformed_err) catch unreachable);
+//                        };
+//                    },
+//                    else => unreachable,
+//                }
+//            }
+//        }
+//    };
+//}
 
 const test_tmp_dir = "std_event_fs_test";
 
@@ -1397,11 +1397,11 @@ pub const OutStream = struct {
         };
     }
 
-    async<*mem.Allocator> fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
+    fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
         const self = @fieldParentPtr(OutStream, "stream", out_stream);
         const offset = self.offset;
         self.offset += bytes.len;
-        return await (async pwritev(self.loop, self.fd, [][]const u8{bytes}, offset) catch unreachable);
+        return pwritev(self.loop, self.fd, [][]const u8{bytes}, offset);
     }
 };
 
@@ -1423,9 +1423,9 @@ pub const InStream = struct {
         };
     }
 
-    async<*mem.Allocator> fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
+    fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
         const self = @fieldParentPtr(InStream, "stream", in_stream);
-        const amt = try await (async preadv(self.loop, self.fd, [][]u8{bytes}, self.offset) catch unreachable);
+        const amt = try preadv(self.loop, self.fd, [][]u8{bytes}, self.offset);
         self.offset += amt;
         return amt;
     }
std/event/loop.zig
@@ -98,9 +98,21 @@ pub const Loop = struct {
     };
     pub const instance: ?*Loop = if (@hasDecl(root, "event_loop")) root.event_loop else default_instance;
 
+    /// TODO copy elision / named return values so that the threads referencing *Loop
+    /// have the correct pointer value.
+    /// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
+    pub fn init(self: *Loop, allocator: *mem.Allocator) !void {
+        if (builtin.single_threaded) {
+            return self.initSingleThreaded(allocator);
+        } else {
+            return self.initMultiThreaded(allocator);
+        }
+    }
+
     /// After initialization, call run().
     /// TODO copy elision / named return values so that the threads referencing *Loop
     /// have the correct pointer value.
+    /// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
     pub fn initSingleThreaded(self: *Loop, allocator: *mem.Allocator) !void {
         return self.initInternal(allocator, 1);
     }
@@ -110,6 +122,7 @@ pub const Loop = struct {
     /// After initialization, call run().
     /// TODO copy elision / named return values so that the threads referencing *Loop
     /// have the correct pointer value.
+    /// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
     pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {
         if (builtin.single_threaded) @compileError("initMultiThreaded unavailable when building in single-threaded mode");
         const core_count = try Thread.cpuCount();
@@ -161,18 +174,18 @@ pub const Loop = struct {
     fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
         switch (builtin.os) {
             .linux => {
-                // TODO self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
-                // TODO self.os_data.fs_queue_item = 0;
-                // TODO // we need another thread for the file system because Linux does not have an async
-                // TODO // file system I/O API.
-                // TODO self.os_data.fs_end_request = fs.RequestNode{
-                // TODO     .prev = undefined,
-                // TODO     .next = undefined,
-                // TODO     .data = fs.Request{
-                // TODO         .msg = fs.Request.Msg.End,
-                // TODO         .finish = fs.Request.Finish.NoAction,
-                // TODO     },
-                // TODO };
+                self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
+                self.os_data.fs_queue_item = 0;
+                // we need another thread for the file system because Linux does not have an async
+                // file system I/O API.
+                self.os_data.fs_end_request = fs.RequestNode{
+                    .prev = undefined,
+                    .next = undefined,
+                    .data = fs.Request{
+                        .msg = fs.Request.Msg.End,
+                        .finish = fs.Request.Finish.NoAction,
+                    },
+                };
 
                 errdefer {
                     while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
@@ -210,10 +223,10 @@ pub const Loop = struct {
                     &self.os_data.final_eventfd_event,
                 );
 
-                // TODO self.os_data.fs_thread = try Thread.spawn(self, posixFsRun);
+                self.os_data.fs_thread = try Thread.spawn(self, posixFsRun);
                 errdefer {
-                    // TODO self.posixFsRequest(&self.os_data.fs_end_request);
-                    // TODO self.os_data.fs_thread.wait();
+                    self.posixFsRequest(&self.os_data.fs_end_request);
+                    self.os_data.fs_thread.wait();
                 }
 
                 if (builtin.single_threaded) {
@@ -315,10 +328,10 @@ pub const Loop = struct {
                     .udata = undefined,
                 };
 
-                // TODO self.os_data.fs_thread = try Thread.spawn(self, posixFsRun);
+                self.os_data.fs_thread = try Thread.spawn(self, posixFsRun);
                 errdefer {
-                    // TODO self.posixFsRequest(&self.os_data.fs_end_request);
-                    // TODO self.os_data.fs_thread.wait();
+                    self.posixFsRequest(&self.os_data.fs_end_request);
+                    self.os_data.fs_thread.wait();
                 }
 
                 if (builtin.single_threaded) {
@@ -441,7 +454,6 @@ pub const Loop = struct {
     pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
         defer self.linuxRemoveFd(fd);
         suspend {
-            // TODO explicitly put this memory in the coroutine frame #1194
             var resume_node = ResumeNode.Basic{
                 .base = ResumeNode{
                     .id = ResumeNode.Id.Basic,
@@ -454,10 +466,6 @@ pub const Loop = struct {
     }
 
     pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !os.Kevent {
-        // TODO #1194
-        suspend {
-            resume @handle();
-        }
         var resume_node = ResumeNode.Basic{
             .base = ResumeNode{
                 .id = ResumeNode.Id.Basic,
@@ -578,7 +586,7 @@ pub const Loop = struct {
             .macosx,
             .freebsd,
             .netbsd,
-            => {}, // TODO self.os_data.fs_thread.wait(),
+            => self.os_data.fs_thread.wait(),
             else => {},
         }
 
@@ -631,7 +639,7 @@ pub const Loop = struct {
             // cause all the threads to stop
             switch (builtin.os) {
                 .linux => {
-                    // TODO self.posixFsRequest(&self.os_data.fs_end_request);
+                    self.posixFsRequest(&self.os_data.fs_end_request);
                     // writing 8 bytes to an eventfd cannot fail
                     os.write(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
                     return;
@@ -862,10 +870,10 @@ pub const Loop = struct {
         epollfd: i32,
         final_eventfd: i32,
         final_eventfd_event: os.linux.epoll_event,
-        // TODO fs_thread: *Thread,
-        // TODO fs_queue_item: i32,
-        // TODO fs_queue: std.atomic.Queue(fs.Request),
-        // TODO fs_end_request: fs.RequestNode,
+        fs_thread: *Thread,
+        fs_queue_item: i32,
+        fs_queue: std.atomic.Queue(fs.Request),
+        fs_end_request: fs.RequestNode,
     };
 };