| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. |
| 4 | // The MIT license requires this copyright notice to be included in all copies | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | // and substantial portions of the software. | 5 | // and substantial portions of the software. |
| 6 | const std = @import("../std.zig"); | 6 | const std = @import("std"); |
| 7 | const builtin = @import("builtin"); | 7 | const builtin = @import("builtin"); |
| 8 | const event = std.event; | 8 | const event = std.event; |
| 9 | const assert = std.debug.assert; | 9 | const assert = std.debug.assert; |
| ... | @@ -24,14 +24,6 @@ const WatchEventId = enum { | ... | @@ -24,14 +24,6 @@ const WatchEventId = enum { |
| 24 | Delete, | 24 | Delete, |
| 25 | }; | 25 | }; |
| 26 | | 26 | |
| 27 | fn eqlString(a: []const u16, b: []const u16) bool { | | |
| 28 | return mem.eql(u16, a, b); | | |
| 29 | } | | |
| 30 | | | |
| 31 | fn hashString(s: []const u16) u32 { | | |
| 32 | return @truncate(u32, std.hash.Wyhash.hash(0, mem.sliceAsBytes(s))); | | |
| 33 | } | | |
| 34 | | | |
| 35 | const WatchEventError = error{ | 27 | const WatchEventError = error{ |
| 36 | UserResourceLimitReached, | 28 | UserResourceLimitReached, |
| 37 | SystemResources, | 29 | SystemResources, |
| ... | @@ -69,21 +61,15 @@ pub fn Watch(comptime V: type) type { | ... | @@ -69,21 +61,15 @@ pub fn Watch(comptime V: type) type { |
| 69 | const WindowsOsData = struct { | 61 | const WindowsOsData = struct { |
| 70 | table_lock: event.Lock, | 62 | table_lock: event.Lock, |
| 71 | dir_table: DirTable, | 63 | dir_table: DirTable, |
| 72 | all_putters: std.atomic.Queue(Put), | 64 | cancelled: bool = false, |
| 73 | ref_count: std.atomic.Int(usize), | | |
| 74 | | | |
| 75 | const Put = struct { | | |
| 76 | putter: anyframe, | | |
| 77 | cancelled: bool = false, | | |
| 78 | }; | | |
| 79 | | 65 | |
| 80 | const DirTable = std.StringHashMap(*Dir); | 66 | const DirTable = std.StringHashMapUnmanaged(*Dir); |
| 81 | const FileTable = std.HashMap([]const u16, V, hashString, eqlString); | 67 | const FileTable = std.StringHashMapUnmanaged(V); |
| 82 | | 68 | |
| 83 | const Dir = struct { | 69 | const Dir = struct { |
| 84 | putter_frame: @Frame(windowsDirReader), | 70 | putter_frame: @Frame(windowsDirReader), |
| 85 | file_table: FileTable, | 71 | file_table: FileTable, |
| 86 | table_lock: event.Lock, | 72 | dir_handle: os.windows.HANDLE, |
| 87 | }; | 73 | }; |
| 88 | }; | 74 | }; |
| 89 | | 75 | |
| ... | @@ -94,8 +80,8 @@ pub fn Watch(comptime V: type) type { | ... | @@ -94,8 +80,8 @@ pub fn Watch(comptime V: type) type { |
| 94 | table_lock: event.Lock, | 80 | table_lock: event.Lock, |
| 95 | cancelled: bool = false, | 81 | cancelled: bool = false, |
| 96 | | 82 | |
| 97 | const WdTable = std.AutoHashMap(i32, Dir); | 83 | const WdTable = std.AutoHashMapUnmanaged(i32, Dir); |
| 98 | const FileTable = std.StringHashMap(V); | 84 | const FileTable = std.StringHashMapUnmanaged(V); |
| 99 | | 85 | |
| 100 | const Dir = struct { | 86 | const Dir = struct { |
| 101 | dirname: []const u8, | 87 | dirname: []const u8, |
| ... | @@ -148,10 +134,9 @@ pub fn Watch(comptime V: type) type { | ... | @@ -148,10 +134,9 @@ pub fn Watch(comptime V: type) type { |
| 148 | .os_data = OsData{ | 134 | .os_data = OsData{ |
| 149 | .table_lock = event.Lock{}, | 135 | .table_lock = event.Lock{}, |
| 150 | .dir_table = OsData.DirTable.init(allocator), | 136 | .dir_table = OsData.DirTable.init(allocator), |
| 151 | .ref_count = std.atomic.Int(usize).init(1), | | |
| 152 | .all_putters = std.atomic.Queue(WindowsOsData.Put).init(), | | |
| 153 | }, | 137 | }, |
| 154 | }; | 138 | }; |
| | 139 | |
| 155 | var buf = try allocator.alloc(Event.Error!Event, event_buf_count); | 140 | var buf = try allocator.alloc(Event.Error!Event, event_buf_count); |
| 156 | self.channel.init(buf); | 141 | self.channel.init(buf); |
| 157 | return self; | 142 | return self; |
| ... | @@ -160,12 +145,15 @@ pub fn Watch(comptime V: type) type { | ... | @@ -160,12 +145,15 @@ pub fn Watch(comptime V: type) type { |
| 160 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => { | 145 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => { |
| 161 | self.* = Self{ | 146 | self.* = Self{ |
| 162 | .allocator = allocator, | 147 | .allocator = allocator, |
| 163 | .channel = channel, | 148 | .channel = undefined, |
| 164 | .os_data = OsData{ | 149 | .os_data = OsData{ |
| 165 | .table_lock = event.Lock.init(), | 150 | .table_lock = event.Lock.init(), |
| 166 | .file_table = OsData.FileTable.init(allocator), | 151 | .file_table = OsData.FileTable.init(allocator), |
| 167 | }, | 152 | }, |
| 168 | }; | 153 | }; |
| | 154 | |
| | 155 | var buf = try allocator.alloc(Event.Error!Event, event_buf_count); |
| | 156 | self.channel.init(buf); |
| 169 | return self; | 157 | return self; |
| 170 | }, | 158 | }, |
| 171 | else => @compileError("Unsupported OS"), | 159 | else => @compileError("Unsupported OS"), |
| ... | @@ -206,35 +194,38 @@ pub fn Watch(comptime V: type) type { | ... | @@ -206,35 +194,38 @@ pub fn Watch(comptime V: type) type { |
| 206 | self.allocator.destroy(self); | 194 | self.allocator.destroy(self); |
| 207 | }, | 195 | }, |
| 208 | .windows => { | 196 | .windows => { |
| 209 | while (self.os_data.all_putters.get()) |putter_node| { | 197 | self.os_data.cancelled = true; |
| 210 | putter_node.cancelled = true; | 198 | var dir_it = self.os_data.dir_table.iterator(); |
| 211 | await putter_node.frame; | 199 | while (dir_it.next()) |dir_entry| { |
| | 200 | if (windows.kernel32.CancelIoEx(dir_entry.value.dir_handle, null) != 0) { |
| | 201 | // We canceled the pending ReadDirectoryChangesW operation, but our |
| | 202 | // frame is still suspending, now waiting indefinitely. |
| | 203 | // Thus, it is safe to resume it ourslves |
| | 204 | resume dir_entry.value.putter_frame; |
| | 205 | } else { |
| | 206 | std.debug.assert(windows.kernel32.GetLastError() == .NOT_FOUND); |
| | 207 | // We are at another suspend point, we can await safely for the |
| | 208 | // function to exit the loop |
| | 209 | await dir_entry.value.putter_frame; |
| | 210 | } |
| | 211 | |
| | 212 | self.allocator.free(dir_entry.key); |
| | 213 | var file_it = dir_entry.value.file_table.iterator(); |
| | 214 | while (file_it.next()) |file_entry| { |
| | 215 | self.allocator.free(file_entry.key); |
| | 216 | } |
| | 217 | dir_entry.value.file_table.deinit(self.allocator); |
| | 218 | self.allocator.destroy(dir_entry.value); |
| 212 | } | 219 | } |
| 213 | self.deref(); | 220 | self.os_data.dir_table.deinit(self.allocator); |
| | 221 | self.allocator.free(self.channel.buffer_nodes); |
| | 222 | self.channel.deinit(); |
| | 223 | self.allocator.destroy(self); |
| 214 | }, | 224 | }, |
| 215 | else => @compileError("Unsupported OS"), | 225 | else => @compileError("Unsupported OS"), |
| 216 | } | 226 | } |
| 217 | } | 227 | } |
| 218 | | 228 | |
| 219 | fn ref(self: *Self) void { | | |
| 220 | _ = self.os_data.ref_count.incr(); | | |
| 221 | } | | |
| 222 | | | |
| 223 | fn deref(self: *Self) void { | | |
| 224 | if (self.os_data.ref_count.decr() == 1) { | | |
| 225 | self.os_data.table_lock.deinit(); | | |
| 226 | var it = self.os_data.dir_table.iterator(); | | |
| 227 | while (it.next()) |entry| { | | |
| 228 | self.allocator.free(entry.key); | | |
| 229 | self.allocator.destroy(entry.value); | | |
| 230 | } | | |
| 231 | self.os_data.dir_table.deinit(); | | |
| 232 | self.channel.deinit(); | | |
| 233 | self.allocator.destroy(self.channel.buffer_nodes); | | |
| 234 | self.allocator.destroy(self); | | |
| 235 | } | | |
| 236 | } | | |
| 237 | | | |
| 238 | pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { | 229 | pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { |
| 239 | switch (builtin.os.tag) { | 230 | switch (builtin.os.tag) { |
| 240 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => return addFileKEvent(self, file_path, value), | 231 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => return addFileKEvent(self, file_path, value), |
| ... | @@ -342,7 +333,7 @@ pub fn Watch(comptime V: type) type { | ... | @@ -342,7 +333,7 @@ pub fn Watch(comptime V: type) type { |
| 342 | const held = self.os_data.table_lock.acquire(); | 333 | const held = self.os_data.table_lock.acquire(); |
| 343 | defer held.release(); | 334 | defer held.release(); |
| 344 | | 335 | |
| 345 | const gop = try self.os_data.wd_table.getOrPut(wd); | 336 | const gop = try self.os_data.wd_table.getOrPut(self.allocator, wd); |
| 346 | if (!gop.found_existing) { | 337 | if (!gop.found_existing) { |
| 347 | gop.entry.value = OsData.Dir{ | 338 | gop.entry.value = OsData.Dir{ |
| 348 | .dirname = try self.allocator.dupe(u8, dirname), | 339 | .dirname = try self.allocator.dupe(u8, dirname), |
| ... | @@ -351,7 +342,7 @@ pub fn Watch(comptime V: type) type { | ... | @@ -351,7 +342,7 @@ pub fn Watch(comptime V: type) type { |
| 351 | } | 342 | } |
| 352 | | 343 | |
| 353 | const dir = &gop.entry.value; | 344 | const dir = &gop.entry.value; |
| 354 | const file_table_gop = try dir.file_table.getOrPut(basename); | 345 | const file_table_gop = try dir.file_table.getOrPut(self.allocator, basename); |
| 355 | if (file_table_gop.found_existing) { | 346 | if (file_table_gop.found_existing) { |
| 356 | const prev_value = file_table_gop.entry.value; | 347 | const prev_value = file_table_gop.entry.value; |
| 357 | file_table_gop.entry.value = value; | 348 | file_table_gop.entry.value = value; |
| ... | @@ -365,89 +356,67 @@ pub fn Watch(comptime V: type) type { | ... | @@ -365,89 +356,67 @@ pub fn Watch(comptime V: type) type { |
| 365 | | 356 | |
| 366 | fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { | 357 | fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { |
| 367 | // TODO we might need to convert dirname and basename to canonical file paths ("short"?) | 358 | // TODO we might need to convert dirname and basename to canonical file paths ("short"?) |
| 368 | const dirname = try self.allocator.dupe(u8, std.fs.path.dirname(file_path) orelse "."); | 359 | const dirname = std.fs.path.dirname(file_path) orelse "."; |
| 369 | var dirname_consumed = false; | 360 | var dirname_path_space: windows.PathSpace = undefined; |
| 370 | defer if (!dirname_consumed) self.allocator.free(dirname); | 361 | dirname_path_space.len = try std.unicode.utf8ToUtf16Le(&dirname_path_space.data, dirname); |
| 371 | | 362 | dirname_path_space.data[dirname_path_space.len] = 0; |
| 372 | const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname); | | |
| 373 | defer self.allocator.free(dirname_utf16le); | | |
| 374 | | 363 | |
| 375 | // TODO https://github.com/ziglang/zig/issues/265 | | |
| 376 | const basename = std.fs.path.basename(file_path); | 364 | const basename = std.fs.path.basename(file_path); |
| 377 | const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename); | 365 | var basename_path_space: windows.PathSpace = undefined; |
| 378 | var basename_utf16le_null_consumed = false; | 366 | basename_path_space.len = try std.unicode.utf8ToUtf16Le(&basename_path_space.data, basename); |
| 379 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); | 367 | basename_path_space.data[basename_path_space.len] = 0; |
| 380 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; | | |
| 381 | | | |
| 382 | const dir_handle = try windows.OpenFile(dirname_utf16le, .{ | | |
| 383 | .dir = std.fs.cwd().fd, | | |
| 384 | .access_mask = windows.FILE_LIST_DIRECTORY, | | |
| 385 | .creation = windows.FILE_OPEN, | | |
| 386 | .io_mode = .blocking, | | |
| 387 | .open_dir = true, | | |
| 388 | }); | | |
| 389 | var dir_handle_consumed = false; | | |
| 390 | defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); | | |
| 391 | | 368 | |
| 392 | const held = self.os_data.table_lock.acquire(); | 369 | const held = self.os_data.table_lock.acquire(); |
| 393 | defer held.release(); | 370 | defer held.release(); |
| 394 | | 371 | |
| 395 | const gop = try self.os_data.dir_table.getOrPut(dirname); | 372 | const gop = try self.os_data.dir_table.getOrPut(self.allocator, dirname); |
| 396 | if (gop.found_existing) { | 373 | if (gop.found_existing) { |
| 397 | const dir = gop.kv.value; | 374 | const dir = gop.entry.value; |
| 398 | const held_dir_lock = dir.table_lock.acquire(); | | |
| 399 | defer held_dir_lock.release(); | | |
| 400 | | 375 | |
| 401 | const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null); | 376 | const file_gop = try dir.file_table.getOrPut(self.allocator, basename); |
| 402 | if (file_gop.found_existing) { | 377 | if (file_gop.found_existing) { |
| 403 | const prev_value = file_gop.kv.value; | 378 | const prev_value = file_gop.entry.value; |
| 404 | file_gop.kv.value = value; | 379 | file_gop.entry.value = value; |
| 405 | return prev_value; | 380 | return prev_value; |
| 406 | } else { | 381 | } else { |
| 407 | file_gop.kv.value = value; | 382 | file_gop.entry.value = value; |
| 408 | basename_utf16le_null_consumed = true; | 383 | file_gop.entry.key = try self.allocator.dupe(u8, basename); |
| 409 | return null; | 384 | return null; |
| 410 | } | 385 | } |
| 411 | } else { | 386 | } else { |
| 412 | errdefer _ = self.os_data.dir_table.remove(dirname); | 387 | errdefer _ = self.os_data.dir_table.remove(dirname); |
| | 388 | const dir_handle = try windows.OpenFile(dirname_path_space.span(), .{ |
| | 389 | .dir = std.fs.cwd().fd, |
| | 390 | .access_mask = windows.FILE_LIST_DIRECTORY, |
| | 391 | .creation = windows.FILE_OPEN, |
| | 392 | .io_mode = .evented, |
| | 393 | .open_dir = true, |
| | 394 | }); |
| | 395 | errdefer windows.CloseHandle(dir_handle); |
| | 396 | |
| 413 | const dir = try self.allocator.create(OsData.Dir); | 397 | const dir = try self.allocator.create(OsData.Dir); |
| 414 | errdefer self.allocator.destroy(dir); | 398 | errdefer self.allocator.destroy(dir); |
| 415 | | 399 | |
| | 400 | gop.entry.key = try self.allocator.dupe(u8, dirname); |
| | 401 | errdefer self.allocator.free(gop.entry.key); |
| | 402 | |
| 416 | dir.* = OsData.Dir{ | 403 | dir.* = OsData.Dir{ |
| 417 | .file_table = OsData.FileTable.init(self.allocator), | 404 | .file_table = OsData.FileTable.init(self.allocator), |
| 418 | .table_lock = event.Lock.init(), | | |
| 419 | .putter_frame = undefined, | 405 | .putter_frame = undefined, |
| | 406 | .dir_handle = dir_handle, |
| 420 | }; | 407 | }; |
| 421 | gop.kv.value = dir; | 408 | gop.entry.value = dir; |
| 422 | assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null); | 409 | try dir.file_table.put(self.allocator, try self.allocator.dupe(u8, basename), value); |
| 423 | basename_utf16le_null_consumed = true; | 410 | dir.putter_frame = async self.windowsDirReader(dir, gop.entry.key); |
| 424 | | | |
| 425 | dir.putter_frame = async self.windowsDirReader(dir_handle, dir); | | |
| 426 | dir_handle_consumed = true; | | |
| 427 | | | |
| 428 | dirname_consumed = true; | | |
| 429 | | | |
| 430 | return null; | 411 | return null; |
| 431 | } | 412 | } |
| 432 | } | 413 | } |
| 433 | | 414 | |
| 434 | fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void { | 415 | fn windowsDirReader(self: *Self, dir: *OsData.Dir, dirname: []const u8) void { |
| 435 | self.ref(); | 416 | defer os.close(dir.dir_handle); |
| 436 | defer self.deref(); | | |
| 437 | | | |
| 438 | defer os.close(dir_handle); | | |
| 439 | | | |
| 440 | var putter_node = std.atomic.Queue(anyframe).Node{ | | |
| 441 | .data = .{ .putter = @frame() }, | | |
| 442 | .prev = null, | | |
| 443 | .next = null, | | |
| 444 | }; | | |
| 445 | self.os_data.all_putters.put(&putter_node); | | |
| 446 | defer _ = self.os_data.all_putters.remove(&putter_node); | | |
| 447 | | | |
| 448 | var resume_node = Loop.ResumeNode.Basic{ | 417 | var resume_node = Loop.ResumeNode.Basic{ |
| 449 | .base = Loop.ResumeNode{ | 418 | .base = Loop.ResumeNode{ |
| 450 | .id = Loop.ResumeNode.Id.Basic, | 419 | .id = .Basic, |
| 451 | .handle = @frame(), | 420 | .handle = @frame(), |
| 452 | .overlapped = windows.OVERLAPPED{ | 421 | .overlapped = windows.OVERLAPPED{ |
| 453 | .Internal = 0, | 422 | .Internal = 0, |
| ... | @@ -458,81 +427,75 @@ pub fn Watch(comptime V: type) type { | ... | @@ -458,81 +427,75 @@ pub fn Watch(comptime V: type) type { |
| 458 | }, | 427 | }, |
| 459 | }, | 428 | }, |
| 460 | }; | 429 | }; |
| 461 | var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; | | |
| 462 | | 430 | |
| 463 | // TODO handle this error not in the channel but in the setup | 431 | var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; |
| 464 | _ = windows.CreateIoCompletionPort( | | |
| 465 | dir_handle, | | |
| 466 | global_event_loop.os_data.io_port, | | |
| 467 | undefined, | | |
| 468 | undefined, | | |
| 469 | ) catch |err| { | | |
| 470 | self.channel.put(err); | | |
| 471 | return; | | |
| 472 | }; | | |
| 473 | | 432 | |
| 474 | while (!putter_node.data.cancelled) { | 433 | global_event_loop.beginOneEvent(); |
| 475 | { | 434 | defer global_event_loop.finishOneEvent(); |
| 476 | // TODO only 1 beginOneEvent for the whole function | 435 | |
| 477 | global_event_loop.beginOneEvent(); | 436 | while (!self.os_data.cancelled) main_loop: { |
| 478 | errdefer global_event_loop.finishOneEvent(); | 437 | suspend { |
| 479 | errdefer { | 438 | _ = windows.kernel32.ReadDirectoryChangesW( |
| 480 | _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped); | 439 | dir.dir_handle, |
| 481 | } | 440 | &event_buf, |
| 482 | suspend { | 441 | event_buf.len, |
| 483 | _ = windows.kernel32.ReadDirectoryChangesW( | 442 | windows.FALSE, // watch subtree |
| 484 | dir_handle, | 443 | windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | |
| 485 | &event_buf, | 444 | windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | |
| 486 | @intCast(windows.DWORD, event_buf.len), | 445 | windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | |
| 487 | windows.FALSE, // watch subtree | 446 | windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, |
| 488 | windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | | 447 | null, // number of bytes transferred (unused for async) |
| 489 | windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | | 448 | &resume_node.base.overlapped, |
| 490 | windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | | 449 | null, // completion routine - unused because we use IOCP |
| 491 | windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, | 450 | ); |
| 492 | null, // number of bytes transferred (unused for async) | | |
| 493 | &resume_node.base.overlapped, | | |
| 494 | null, // completion routine - unused because we use IOCP | | |
| 495 | ); | | |
| 496 | } | | |
| 497 | } | 451 | } |
| | 452 | |
| 498 | var bytes_transferred: windows.DWORD = undefined; | 453 | var bytes_transferred: windows.DWORD = undefined; |
| 499 | if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { | 454 | if (windows.kernel32.GetOverlappedResult( |
| 500 | const err = switch (windows.kernel32.GetLastError()) { | 455 | dir.dir_handle, |
| | 456 | &resume_node.base.overlapped, |
| | 457 | &bytes_transferred, |
| | 458 | windows.FALSE, |
| | 459 | ) == 0) { |
| | 460 | const potential_error = windows.kernel32.GetLastError(); |
| | 461 | const err = switch (potential_error) { |
| | 462 | .OPERATION_ABORTED, .IO_INCOMPLETE => err_blk: { |
| | 463 | if (self.os_data.cancelled) |
| | 464 | break :main_loop |
| | 465 | else |
| | 466 | break :err_blk windows.unexpectedError(potential_error); |
| | 467 | }, |
| 501 | else => |err| windows.unexpectedError(err), | 468 | else => |err| windows.unexpectedError(err), |
| 502 | }; | 469 | }; |
| 503 | self.channel.put(err); | 470 | self.channel.put(err); |
| 504 | } else { | 471 | } else { |
| 505 | // can't use @bytesToSlice because of the special variable length name field | 472 | var ptr: [*]u8 = &event_buf; |
| 506 | var ptr = event_buf[0..].ptr; | | |
| 507 | const end_ptr = ptr + bytes_transferred; | 473 | const end_ptr = ptr + bytes_transferred; |
| 508 | var ev: *windows.FILE_NOTIFY_INFORMATION = undefined; | 474 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) { |
| 509 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) { | 475 | const ev = @ptrCast(*const windows.FILE_NOTIFY_INFORMATION, ptr); |
| 510 | ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr); | | |
| 511 | const emit = switch (ev.Action) { | 476 | const emit = switch (ev.Action) { |
| 512 | windows.FILE_ACTION_REMOVED => WatchEventId.Delete, | 477 | windows.FILE_ACTION_REMOVED => WatchEventId.Delete, |
| 513 | windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite, | 478 | windows.FILE_ACTION_MODIFIED => .CloseWrite, |
| 514 | else => null, | 479 | else => null, |
| 515 | }; | 480 | }; |
| 516 | if (emit) |id| { | 481 | if (emit) |id| { |
| 517 | const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2]; | 482 | const basename_ptr = @ptrCast([*]u16, ptr + @sizeOf(windows.FILE_NOTIFY_INFORMATION)); |
| 518 | const user_value = blk: { | 483 | const basename_utf16le = basename_ptr[0 .. ev.FileNameLength / 2]; |
| 519 | const held = dir.table_lock.acquire(); | 484 | var basename_data: [std.fs.MAX_PATH_BYTES]u8 = undefined; |
| 520 | defer held.release(); | 485 | const basename = basename_data[0 .. std.unicode.utf16leToUtf8(&basename_data, basename_utf16le) catch unreachable]; |
| 521 | | 486 | |
| 522 | if (dir.file_table.get(basename_utf16le)) |entry| { | 487 | if (dir.file_table.getEntry(basename)) |entry| { |
| 523 | break :blk entry.value; | | |
| 524 | } else { | | |
| 525 | break :blk null; | | |
| 526 | } | | |
| 527 | }; | | |
| 528 | if (user_value) |v| { | | |
| 529 | self.channel.put(Event{ | 488 | self.channel.put(Event{ |
| 530 | .id = id, | 489 | .id = id, |
| 531 | .data = v, | 490 | .data = entry.value, |
| | 491 | .dirname = dirname, |
| | 492 | .basename = entry.key, |
| 532 | }); | 493 | }); |
| 533 | } | 494 | } |
| 534 | } | 495 | } |
| | 496 | |
| 535 | if (ev.NextEntryOffset == 0) break; | 497 | if (ev.NextEntryOffset == 0) break; |
| | 498 | ptr = @alignCast(@alignOf(windows.FILE_NOTIFY_INFORMATION), ptr + ev.NextEntryOffset); |
| 536 | } | 499 | } |
| 537 | } | 500 | } |
| 538 | } | 501 | } |
| ... | @@ -554,8 +517,21 @@ pub fn Watch(comptime V: type) type { | ... | @@ -554,8 +517,21 @@ pub fn Watch(comptime V: type) type { |
| 554 | } | 517 | } |
| 555 | return null; | 518 | return null; |
| 556 | }, | 519 | }, |
| | 520 | .windows => { |
| | 521 | const dirname = std.fs.path.dirname(file_path) orelse "."; |
| | 522 | const basename = std.fs.path.basename(file_path); |
| | 523 | |
| | 524 | const held = self.os_data.table_lock.acquire(); |
| | 525 | defer held.release(); |
| | 526 | |
| | 527 | const dir = self.os_data.dir_table.get(dirname) orelse return null; |
| | 528 | if (dir.file_table.remove(basename)) |file_entry| { |
| | 529 | self.allocator.free(file_entry.key); |
| | 530 | return file_entry.value; |
| | 531 | } |
| | 532 | return null; |
| | 533 | }, |
| 557 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => @panic("TODO"), | 534 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => @panic("TODO"), |
| 558 | .windows => return @panic("TODO"), | | |
| 559 | else => @compileError("Unsupported OS"), | 535 | else => @compileError("Unsupported OS"), |
| 560 | } | 536 | } |
| 561 | } | 537 | } |
| ... | @@ -565,7 +541,7 @@ pub fn Watch(comptime V: type) type { | ... | @@ -565,7 +541,7 @@ pub fn Watch(comptime V: type) type { |
| 565 | | 541 | |
| 566 | defer { | 542 | defer { |
| 567 | std.debug.assert(self.os_data.wd_table.count() == 0); | 543 | std.debug.assert(self.os_data.wd_table.count() == 0); |
| 568 | self.os_data.wd_table.deinit(); | 544 | self.os_data.wd_table.deinit(self.allocator); |
| 569 | os.close(self.os_data.inotify_fd); | 545 | os.close(self.os_data.inotify_fd); |
| 570 | self.allocator.free(self.channel.buffer_nodes); | 546 | self.allocator.free(self.channel.buffer_nodes); |
| 571 | self.channel.deinit(); | 547 | self.channel.deinit(); |
| ... | @@ -585,9 +561,6 @@ pub fn Watch(comptime V: type) type { | ... | @@ -585,9 +561,6 @@ pub fn Watch(comptime V: type) type { |
| 585 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); | 561 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); |
| 586 | const basename = std.mem.span(@ptrCast([*:0]u8, basename_ptr)); | 562 | const basename = std.mem.span(@ptrCast([*:0]u8, basename_ptr)); |
| 587 | | 563 | |
| 588 | const held = self.os_data.table_lock.acquire(); | | |
| 589 | defer held.release(); | | |
| 590 | | | |
| 591 | const dir = &self.os_data.wd_table.get(ev.wd).?; | 564 | const dir = &self.os_data.wd_table.get(ev.wd).?; |
| 592 | if (dir.file_table.getEntry(basename)) |file_value| { | 565 | if (dir.file_table.getEntry(basename)) |file_value| { |
| 593 | self.channel.put(Event{ | 566 | self.channel.put(Event{ |
| ... | @@ -607,17 +580,14 @@ pub fn Watch(comptime V: type) type { | ... | @@ -607,17 +580,14 @@ pub fn Watch(comptime V: type) type { |
| 607 | self.allocator.free(file_entry.key); | 580 | self.allocator.free(file_entry.key); |
| 608 | } | 581 | } |
| 609 | self.allocator.free(wd_entry.value.dirname); | 582 | self.allocator.free(wd_entry.value.dirname); |
| 610 | wd_entry.value.file_table.deinit(); | 583 | wd_entry.value.file_table.deinit(self.allocator); |
| 611 | } | 584 | } |
| 612 | } else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) { | 585 | } else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) { |
| 613 | // File or directory was removed or deleted | 586 | // File or directory was removed or deleted |
| 614 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); | 587 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); |
| 615 | const basename = std.mem.span(@ptrCast([*:0]u8, basename_ptr)); | 588 | const basename = std.mem.span(@ptrCast([*:0]u8, basename_ptr)); |
| 616 | | 589 | |
| 617 | const held = self.os_data.table_lock.acquire(); | | |
| 618 | defer held.release(); | | |
| 619 | const dir = &self.os_data.wd_table.get(ev.wd).?; | 590 | const dir = &self.os_data.wd_table.get(ev.wd).?; |
| 620 | | | |
| 621 | if (dir.file_table.getEntry(basename)) |file_value| { | 591 | if (dir.file_table.getEntry(basename)) |file_value| { |
| 622 | self.channel.put(Event{ | 592 | self.channel.put(Event{ |
| 623 | .id = .Delete, | 593 | .id = .Delete, |