| author | |
| committer | |
| log | 60955feab82ef256a8983517f7435cde797c4e84 |
| tree | 10b78e4e2b09254e2777724955d6a96dd9b787b5 |
| parent | 5cbfe392beb26520554e7ec6ae7c67df47cc7e04 |
TODO: after 1 event emitted for a deleted file, the file is no longer
watched3 files changed, 93 insertions(+), 48 deletions(-)
src-self-hosted/compilation.zig+11-15| ... | @@ -758,32 +758,28 @@ pub const Compilation = struct { | ... | @@ -758,32 +758,28 @@ pub const Compilation = struct { |
| 758 | // First, get an item from the watch channel, waiting on the channel. | 758 | // First, get an item from the watch channel, waiting on the channel. |
| 759 | var group = event.Group(BuildError!void).init(self.loop); | 759 | var group = event.Group(BuildError!void).init(self.loop); |
| 760 | { | 760 | { |
| 761 | const ev = await (async self.fs_watch.channel.get() catch unreachable); | 761 | const ev = (await (async self.fs_watch.channel.get() catch unreachable)) catch |err| { |
| 762 | const root_scope = switch (ev) { | 762 | build_result = err; |
| 763 | fs.Watch(*Scope.Root).Event.CloseWrite => |x| x, | 763 | continue; |
| 764 | fs.Watch(*Scope.Root).Event.Err => |err| { | ||
| 765 | build_result = err; | ||
| 766 | continue; | ||
| 767 | }, | ||
| 768 | }; | 764 | }; |
| 765 | const root_scope = ev.data; | ||
| 769 | group.call(rebuildFile, self, root_scope) catch |err| { | 766 | group.call(rebuildFile, self, root_scope) catch |err| { |
| 770 | build_result = err; | 767 | build_result = err; |
| 771 | continue; | 768 | continue; |
| 772 | }; | 769 | }; |
| 773 | } | 770 | } |
| 774 | // Next, get all the items from the channel that are buffered up. | 771 | // Next, get all the items from the channel that are buffered up. |
| 775 | while (await (async self.fs_watch.channel.getOrNull() catch unreachable)) |ev| { | 772 | while (await (async self.fs_watch.channel.getOrNull() catch unreachable)) |ev_or_err| { |
| 776 | const root_scope = switch (ev) { | 773 | if (ev_or_err) |ev| { |
| 777 | fs.Watch(*Scope.Root).Event.CloseWrite => |x| x, | 774 | const root_scope = ev.data; |
| 778 | fs.Watch(*Scope.Root).Event.Err => |err| { | 775 | group.call(rebuildFile, self, root_scope) catch |err| { |
| 779 | build_result = err; | 776 | build_result = err; |
| 780 | continue; | 777 | continue; |
| 781 | }, | 778 | }; |
| 782 | }; | 779 | } else |err| { |
| 783 | group.call(rebuildFile, self, root_scope) catch |err| { | ||
| 784 | build_result = err; | 780 | build_result = err; |
| 785 | continue; | 781 | continue; |
| 786 | }; | 782 | } |
| 787 | } | 783 | } |
| 788 | build_result = await (async group.wait() catch unreachable); | 784 | build_result = await (async group.wait() catch unreachable); |
| 789 | } | 785 | } |
std/event/fs.zig+44-21| ... | @@ -358,9 +358,20 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) | ... | @@ -358,9 +358,20 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) |
| 358 | } | 358 | } |
| 359 | } | 359 | } |
| 360 | 360 | ||
| 361 | pub const WatchEventId = enum { | ||
| 362 | CloseWrite, | ||
| 363 | Delete, | ||
| 364 | }; | ||
| 365 | |||
| 366 | pub const WatchEventError = error{ | ||
| 367 | UserResourceLimitReached, | ||
| 368 | SystemResources, | ||
| 369 | AccessDenied, | ||
| 370 | }; | ||
| 371 | |||
| 361 | pub fn Watch(comptime V: type) type { | 372 | pub fn Watch(comptime V: type) type { |
| 362 | return struct { | 373 | return struct { |
| 363 | channel: *event.Channel(Event), | 374 | channel: *event.Channel(Event.Error!Event), |
| 364 | os_data: OsData, | 375 | os_data: OsData, |
| 365 | 376 | ||
| 366 | const OsData = switch (builtin.os) { | 377 | const OsData = switch (builtin.os) { |
| ... | @@ -395,19 +406,16 @@ pub fn Watch(comptime V: type) type { | ... | @@ -395,19 +406,16 @@ pub fn Watch(comptime V: type) type { |
| 395 | file_table: OsData.FileTable, | 406 | file_table: OsData.FileTable, |
| 396 | }; | 407 | }; |
| 397 | 408 | ||
| 398 | pub const Event = union(enum) { | 409 | pub const Event = struct { |
| 399 | CloseWrite: V, | 410 | id: Id, |
| 400 | Err: Error, | 411 | data: V, |
| 401 | 412 | ||
| 402 | pub const Error = error{ | 413 | pub const Id = WatchEventId; |
| 403 | UserResourceLimitReached, | 414 | pub const Error = WatchEventError; |
| 404 | SystemResources, | ||
| 405 | AccessDenied, | ||
| 406 | }; | ||
| 407 | }; | 415 | }; |
| 408 | 416 | ||
| 409 | pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self { | 417 | pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self { |
| 410 | const channel = try event.Channel(Self.Event).create(loop, event_buf_count); | 418 | const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count); |
| 411 | errdefer channel.destroy(); | 419 | errdefer channel.destroy(); |
| 412 | 420 | ||
| 413 | switch (builtin.os) { | 421 | switch (builtin.os) { |
| ... | @@ -519,19 +527,32 @@ pub fn Watch(comptime V: type) type { | ... | @@ -519,19 +527,32 @@ pub fn Watch(comptime V: type) type { |
| 519 | } | 527 | } |
| 520 | 528 | ||
| 521 | while (true) { | 529 | while (true) { |
| 522 | (await (async self.channel.loop.bsdWaitKev( | 530 | if (await (async self.channel.loop.bsdWaitKev( |
| 523 | @intCast(usize, close_op.getHandle()), posix.EVFILT_VNODE, posix.NOTE_WRITE, | 531 | @intCast(usize, close_op.getHandle()), |
| 524 | ) catch unreachable)) catch |err| switch (err) { | 532 | posix.EVFILT_VNODE, |
| 533 | posix.NOTE_WRITE | posix.NOTE_DELETE, | ||
| 534 | ) catch unreachable)) |kev| { | ||
| 535 | // TODO handle EV_ERROR | ||
| 536 | if (kev.fflags & posix.NOTE_DELETE != 0) { | ||
| 537 | await (async self.channel.put(Self.Event{ | ||
| 538 | .id = Event.Id.Delete, | ||
| 539 | .data = value_copy, | ||
| 540 | }) catch unreachable); | ||
| 541 | } else if (kev.fflags & posix.NOTE_WRITE != 0) { | ||
| 542 | await (async self.channel.put(Self.Event{ | ||
| 543 | .id = Event.Id.CloseWrite, | ||
| 544 | .data = value_copy, | ||
| 545 | }) catch unreachable); | ||
| 546 | } | ||
| 547 | } else |err| switch (err) { | ||
| 525 | error.EventNotFound => unreachable, | 548 | error.EventNotFound => unreachable, |
| 526 | error.ProcessNotFound => unreachable, | 549 | error.ProcessNotFound => unreachable, |
| 527 | error.AccessDenied, error.SystemResources => { | 550 | error.AccessDenied, error.SystemResources => { |
| 528 | // TODO https://github.com/ziglang/zig/issues/769 | 551 | // TODO https://github.com/ziglang/zig/issues/769 |
| 529 | const casted_err = @errSetCast(error{AccessDenied,SystemResources}, err); | 552 | const casted_err = @errSetCast(error{AccessDenied,SystemResources}, err); |
| 530 | await (async self.channel.put(Self.Event{ .Err = casted_err }) catch unreachable); | 553 | await (async self.channel.put(casted_err) catch unreachable); |
| 531 | }, | 554 | }, |
| 532 | }; | 555 | } |
| 533 | |||
| 534 | await (async self.channel.put(Self.Event{ .CloseWrite = value_copy }) catch unreachable); | ||
| 535 | } | 556 | } |
| 536 | } | 557 | } |
| 537 | 558 | ||
| ... | @@ -582,7 +603,7 @@ pub fn Watch(comptime V: type) type { | ... | @@ -582,7 +603,7 @@ pub fn Watch(comptime V: type) type { |
| 582 | @panic("TODO"); | 603 | @panic("TODO"); |
| 583 | } | 604 | } |
| 584 | 605 | ||
| 585 | async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void { | 606 | async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void { |
| 586 | // TODO https://github.com/ziglang/zig/issues/1194 | 607 | // TODO https://github.com/ziglang/zig/issues/1194 |
| 587 | suspend { | 608 | suspend { |
| 588 | resume @handle(); | 609 | resume @handle(); |
| ... | @@ -743,9 +764,9 @@ async fn testFsWatch(loop: *event.Loop) !void { | ... | @@ -743,9 +764,9 @@ async fn testFsWatch(loop: *event.Loop) !void { |
| 743 | } | 764 | } |
| 744 | 765 | ||
| 745 | ev_consumed = true; | 766 | ev_consumed = true; |
| 746 | switch (await ev) { | 767 | switch ((try await ev).id) { |
| 747 | Watch(void).Event.CloseWrite => {}, | 768 | WatchEventId.CloseWrite => {}, |
| 748 | Watch(void).Event.Err => |err| return err, | 769 | WatchEventId.Delete => @panic("wrong event"), |
| 749 | } | 770 | } |
| 750 | 771 | ||
| 751 | const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024); | 772 | const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024); |
| ... | @@ -753,4 +774,6 @@ async fn testFsWatch(loop: *event.Loop) !void { | ... | @@ -753,4 +774,6 @@ async fn testFsWatch(loop: *event.Loop) !void { |
| 753 | \\line 1 | 774 | \\line 1 |
| 754 | \\lorem ipsum | 775 | \\lorem ipsum |
| 755 | )); | 776 | )); |
| 777 | |||
| 778 | // TODO test deleting the file and then re-adding it. we should get events for both | ||
| 756 | } | 779 | } |
std/event/loop.zig+38-12| ... | @@ -52,6 +52,20 @@ pub const Loop = struct { | ... | @@ -52,6 +52,20 @@ pub const Loop = struct { |
| 52 | base: ResumeNode, | 52 | base: ResumeNode, |
| 53 | kevent: posix.Kevent, | 53 | kevent: posix.Kevent, |
| 54 | }; | 54 | }; |
| 55 | |||
| 56 | pub const Basic = switch (builtin.os) { | ||
| 57 | builtin.Os.macosx => struct { | ||
| 58 | base: ResumeNode, | ||
| 59 | kev: posix.Kevent, | ||
| 60 | }, | ||
| 61 | builtin.Os.linux => struct { | ||
| 62 | base: ResumeNode, | ||
| 63 | }, | ||
| 64 | builtin.Os.windows => struct { | ||
| 65 | base: ResumeNode, | ||
| 66 | }, | ||
| 67 | else => @compileError("unsupported OS"), | ||
| 68 | }; | ||
| 55 | }; | 69 | }; |
| 56 | 70 | ||
| 57 | /// After initialization, call run(). | 71 | /// After initialization, call run(). |
| ... | @@ -379,28 +393,37 @@ pub const Loop = struct { | ... | @@ -379,28 +393,37 @@ pub const Loop = struct { |
| 379 | defer self.linuxRemoveFd(fd); | 393 | defer self.linuxRemoveFd(fd); |
| 380 | suspend { | 394 | suspend { |
| 381 | // TODO explicitly put this memory in the coroutine frame #1194 | 395 | // TODO explicitly put this memory in the coroutine frame #1194 |
| 382 | var resume_node = ResumeNode{ | 396 | var resume_node = ResumeNode.Basic{ |
| 383 | .id = ResumeNode.Id.Basic, | 397 | .base = ResumeNode{ |
| 384 | .handle = @handle(), | 398 | .id = ResumeNode.Id.Basic, |
| 399 | .handle = @handle(), | ||
| 400 | }, | ||
| 385 | }; | 401 | }; |
| 386 | try self.linuxAddFd(fd, &resume_node, flags); | 402 | try self.linuxAddFd(fd, &resume_node.base, flags); |
| 387 | } | 403 | } |
| 388 | } | 404 | } |
| 389 | 405 | ||
| 390 | pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !void { | 406 | pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !posix.Kevent { |
| 391 | defer self.bsdRemoveKev(ident, filter); | 407 | // TODO #1194 |
| 392 | suspend { | 408 | suspend { |
| 393 | // TODO explicitly put this memory in the coroutine frame #1194 | 409 | resume @handle(); |
| 394 | var resume_node = ResumeNode{ | 410 | } |
| 411 | var resume_node = ResumeNode.Basic{ | ||
| 412 | .base = ResumeNode{ | ||
| 395 | .id = ResumeNode.Id.Basic, | 413 | .id = ResumeNode.Id.Basic, |
| 396 | .handle = @handle(), | 414 | .handle = @handle(), |
| 397 | }; | 415 | }, |
| 416 | .kev = undefined, | ||
| 417 | }; | ||
| 418 | defer self.bsdRemoveKev(ident, filter); | ||
| 419 | suspend { | ||
| 398 | try self.bsdAddKev(&resume_node, ident, filter, fflags); | 420 | try self.bsdAddKev(&resume_node, ident, filter, fflags); |
| 399 | } | 421 | } |
| 422 | return resume_node.kev; | ||
| 400 | } | 423 | } |
| 401 | 424 | ||
| 402 | /// resume_node must live longer than the promise that it holds a reference to. | 425 | /// resume_node must live longer than the promise that it holds a reference to. |
| 403 | pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode, ident: usize, filter: i16, fflags: u32) !void { | 426 | pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode.Basic, ident: usize, filter: i16, fflags: u32) !void { |
| 404 | self.beginOneEvent(); | 427 | self.beginOneEvent(); |
| 405 | errdefer self.finishOneEvent(); | 428 | errdefer self.finishOneEvent(); |
| 406 | var kev = posix.Kevent{ | 429 | var kev = posix.Kevent{ |
| ... | @@ -409,7 +432,7 @@ pub const Loop = struct { | ... | @@ -409,7 +432,7 @@ pub const Loop = struct { |
| 409 | .flags = posix.EV_ADD|posix.EV_ENABLE|posix.EV_CLEAR, | 432 | .flags = posix.EV_ADD|posix.EV_ENABLE|posix.EV_CLEAR, |
| 410 | .fflags = fflags, | 433 | .fflags = fflags, |
| 411 | .data = 0, | 434 | .data = 0, |
| 412 | .udata = @ptrToInt(resume_node), | 435 | .udata = @ptrToInt(&resume_node.base), |
| 413 | }; | 436 | }; |
| 414 | const kevent_array = (*[1]posix.Kevent)(&kev); | 437 | const kevent_array = (*[1]posix.Kevent)(&kev); |
| 415 | const empty_kevs = ([*]posix.Kevent)(undefined)[0..0]; | 438 | const empty_kevs = ([*]posix.Kevent)(undefined)[0..0]; |
| ... | @@ -632,7 +655,10 @@ pub const Loop = struct { | ... | @@ -632,7 +655,10 @@ pub const Loop = struct { |
| 632 | const handle = resume_node.handle; | 655 | const handle = resume_node.handle; |
| 633 | const resume_node_id = resume_node.id; | 656 | const resume_node_id = resume_node.id; |
| 634 | switch (resume_node_id) { | 657 | switch (resume_node_id) { |
| 635 | ResumeNode.Id.Basic => {}, | 658 | ResumeNode.Id.Basic => { |
| 659 | const basic_node = @fieldParentPtr(ResumeNode.Basic, "base", resume_node); | ||
| 660 | basic_node.kev = ev; | ||
| 661 | }, | ||
| 636 | ResumeNode.Id.Stop => return, | 662 | ResumeNode.Id.Stop => return, |
| 637 | ResumeNode.Id.EventFd => { | 663 | ResumeNode.Id.EventFd => { |
| 638 | const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node); | 664 | const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node); |