| ... | @@ -605,7 +605,16 @@ const Os = switch (builtin.os.tag) { | ... | @@ -605,7 +605,16 @@ const Os = switch (builtin.os.tag) { |
| 605 | | 605 | |
| 606 | kq_fd: i32, | 606 | kq_fd: i32, |
| 607 | /// Indexes correspond 1:1 with `dir_table`. | 607 | /// Indexes correspond 1:1 with `dir_table`. |
| 608 | reaction_sets: std.ArrayListUnmanaged(ReactionSet), | 608 | handles: std.MultiArrayList(struct { |
| | 609 | rs: ReactionSet, |
| | 610 | /// If the corresponding dir_table Path has sub_path == "", then it |
| | 611 | /// suffices as the open directory handle, and this value will be |
| | 612 | /// -1. Otherwise, it needs to be opened in update(), and will be |
| | 613 | /// stored here. |
| | 614 | dir_fd: i32, |
| | 615 | /// Number of files being watched by this directory handle. |
| | 616 | ref_count: u32, |
| | 617 | }), |
| 609 | | 618 | |
| 610 | const dir_open_flags: posix.O = f: { | 619 | const dir_open_flags: posix.O = f: { |
| 611 | var f: posix.O = .{ | 620 | var f: posix.O = .{ |
| ... | @@ -619,6 +628,9 @@ const Os = switch (builtin.os.tag) { | ... | @@ -619,6 +628,9 @@ const Os = switch (builtin.os.tag) { |
| 619 | break :f f; | 628 | break :f f; |
| 620 | }; | 629 | }; |
| 621 | | 630 | |
| | 631 | const EV = std.c.EV; |
| | 632 | const NOTE = std.c.NOTE; |
| | 633 | |
| 622 | fn init() !Watch { | 634 | fn init() !Watch { |
| 623 | const kq_fd = try posix.kqueue(); | 635 | const kq_fd = try posix.kqueue(); |
| 624 | errdefer posix.close(kq_fd); | 636 | errdefer posix.close(kq_fd); |
| ... | @@ -626,27 +638,29 @@ const Os = switch (builtin.os.tag) { | ... | @@ -626,27 +638,29 @@ const Os = switch (builtin.os.tag) { |
| 626 | .dir_table = .{}, | 638 | .dir_table = .{}, |
| 627 | .os = .{ | 639 | .os = .{ |
| 628 | .kq_fd = kq_fd, | 640 | .kq_fd = kq_fd, |
| 629 | .reaction_sets = .{}, | 641 | .handles = .empty, |
| 630 | }, | 642 | }, |
| 631 | .generation = 0, | 643 | .generation = 0, |
| 632 | }; | 644 | }; |
| 633 | } | 645 | } |
| 634 | | 646 | |
| 635 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { | 647 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { |
| | 648 | const handles = &w.os.handles; |
| 636 | for (steps) |step| { | 649 | for (steps) |step| { |
| 637 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | 650 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { |
| 638 | const reaction_set = rs: { | 651 | const reaction_set = rs: { |
| 639 | const gop = try w.dir_table.getOrPut(gpa, path); | 652 | const gop = try w.dir_table.getOrPut(gpa, path); |
| 640 | if (!gop.found_existing) { | 653 | if (!gop.found_existing) { |
| 641 | const dir_fd = if (path.sub_path.len == 0) | 654 | const skip_open_dir = path.sub_path.len == 0; |
| | 655 | const dir_fd = if (skip_open_dir) |
| 642 | path.root_dir.handle.fd | 656 | path.root_dir.handle.fd |
| 643 | else | 657 | else |
| 644 | posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |err| { | 658 | posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |err| { |
| 645 | fatal("failed to open directory {}: {s}", .{ path, @errorName(err) }); | 659 | fatal("failed to open directory {}: {s}", .{ path, @errorName(err) }); |
| 646 | }; | 660 | }; |
| 647 | const EV = std.c.EV; | 661 | // Empirically the dir has to stay open or else no events are triggered. |
| 648 | const NOTE = std.c.NOTE; | 662 | errdefer if (!skip_open_dir) posix.close(dir_fd); |
| 649 | var changes = [1]posix.Kevent{.{ | 663 | const changes = [1]posix.Kevent{.{ |
| 650 | .ident = @bitCast(@as(isize, dir_fd)), | 664 | .ident = @bitCast(@as(isize, dir_fd)), |
| 651 | .filter = std.c.EVFILT.VNODE, | 665 | .filter = std.c.EVFILT.VNODE, |
| 652 | .flags = EV.ADD | EV.ENABLE | EV.CLEAR, | 666 | .flags = EV.ADD | EV.ENABLE | EV.CLEAR, |
| ... | @@ -655,12 +669,16 @@ const Os = switch (builtin.os.tag) { | ... | @@ -655,12 +669,16 @@ const Os = switch (builtin.os.tag) { |
| 655 | .udata = gop.index, | 669 | .udata = gop.index, |
| 656 | }}; | 670 | }}; |
| 657 | _ = try posix.kevent(w.os.kq_fd, &changes, &.{}, null); | 671 | _ = try posix.kevent(w.os.kq_fd, &changes, &.{}, null); |
| 658 | assert(w.os.reaction_sets.items.len == gop.index); | 672 | assert(handles.len == gop.index); |
| 659 | const reaction_set = try w.os.reaction_sets.addOne(gpa); | 673 | try handles.append(gpa, .{ |
| 660 | reaction_set.* = .{}; | 674 | .rs = .{}, |
| 661 | break :rs reaction_set; | 675 | .dir_fd = if (skip_open_dir) -1 else dir_fd, |
| | 676 | .ref_count = 1, |
| | 677 | }); |
| | 678 | } else { |
| | 679 | handles.items(.ref_count)[gop.index] += 1; |
| 662 | } | 680 | } |
| 663 | break :rs &w.os.reaction_sets.items[gop.index]; | 681 | break :rs &handles.items(.rs)[gop.index]; |
| 664 | }; | 682 | }; |
| 665 | for (files.items) |basename| { | 683 | for (files.items) |basename| { |
| 666 | const gop = try reaction_set.getOrPut(gpa, basename); | 684 | const gop = try reaction_set.getOrPut(gpa, basename); |
| ... | @@ -672,47 +690,86 @@ const Os = switch (builtin.os.tag) { | ... | @@ -672,47 +690,86 @@ const Os = switch (builtin.os.tag) { |
| 672 | | 690 | |
| 673 | { | 691 | { |
| 674 | // Remove marks for files that are no longer inputs. | 692 | // Remove marks for files that are no longer inputs. |
| 675 | //var i: usize = 0; | 693 | var i: usize = 0; |
| 676 | //while (i < w.os.handle_table.entries.len) { | 694 | while (i < handles.len) { |
| 677 | // { | 695 | { |
| 678 | // const reaction_set = &w.os.handle_table.values()[i]; | 696 | const reaction_set = &handles.items(.rs)[i]; |
| 679 | // var step_set_i: usize = 0; | 697 | var step_set_i: usize = 0; |
| 680 | // while (step_set_i < reaction_set.entries.len) { | 698 | while (step_set_i < reaction_set.entries.len) { |
| 681 | // const step_set = &reaction_set.values()[step_set_i]; | 699 | const step_set = &reaction_set.values()[step_set_i]; |
| 682 | // var dirent_i: usize = 0; | 700 | var dirent_i: usize = 0; |
| 683 | // while (dirent_i < step_set.entries.len) { | 701 | while (dirent_i < step_set.entries.len) { |
| 684 | // const generations = step_set.values(); | 702 | const generations = step_set.values(); |
| 685 | // if (generations[dirent_i] == w.generation) { | 703 | if (generations[dirent_i] == w.generation) { |
| 686 | // dirent_i += 1; | 704 | dirent_i += 1; |
| 687 | // continue; | 705 | continue; |
| 688 | // } | 706 | } |
| 689 | // step_set.swapRemoveAt(dirent_i); | 707 | step_set.swapRemoveAt(dirent_i); |
| 690 | // } | 708 | } |
| 691 | // if (step_set.entries.len > 0) { | 709 | if (step_set.entries.len > 0) { |
| 692 | // step_set_i += 1; | 710 | step_set_i += 1; |
| 693 | // continue; | 711 | continue; |
| 694 | // } | 712 | } |
| 695 | // reaction_set.swapRemoveAt(step_set_i); | 713 | reaction_set.swapRemoveAt(step_set_i); |
| 696 | // } | 714 | } |
| 697 | // if (reaction_set.entries.len > 0) { | 715 | if (reaction_set.entries.len > 0) { |
| 698 | // i += 1; | 716 | i += 1; |
| 699 | // continue; | 717 | continue; |
| 700 | // } | 718 | } |
| 701 | // } | 719 | } |
| 702 | | 720 | |
| 703 | // const path = w.dir_table.keys()[i]; | 721 | const ref_count_ptr = &handles.items(.ref_count)[i]; |
| 704 | | 722 | ref_count_ptr.* -= 1; |
| 705 | // posix.fanotify_mark(fan_fd, .{ | 723 | if (ref_count_ptr.* > 0) continue; |
| 706 | // .REMOVE = true, | 724 | |
| 707 | // .ONLYDIR = true, | 725 | // If the sub_path == "" then this patch has already the |
| 708 | // }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| switch (err) { | 726 | // dir fd that we need to use as the ident to remove the |
| 709 | // error.FileNotFound => {}, // Expected, harmless. | 727 | // event. If it was opened above with openat() then we need |
| 710 | // else => |e| std.log.warn("unable to unwatch '{}': {s}", .{ path, @errorName(e) }), | 728 | // to access that data via the dir_fd field. |
| 711 | // }; | 729 | const path = w.dir_table.keys()[i]; |
| 712 | | 730 | const dir_fd = if (path.sub_path.len == 0) |
| 713 | // w.dir_table.swapRemoveAt(i); | 731 | path.root_dir.handle.fd |
| 714 | // w.os.handle_table.swapRemoveAt(i); | 732 | else |
| 715 | //} | 733 | handles.items(.dir_fd)[i]; |
| | 734 | assert(dir_fd != -1); |
| | 735 | |
| | 736 | // The changelist also needs to update the udata field of the last |
| | 737 | // event, since we are doing a swap remove, and we store the dir_table |
| | 738 | // index in the udata field. |
| | 739 | const last_dir_fd = fd: { |
| | 740 | const last_path = w.dir_table.keys()[handles.len - 1]; |
| | 741 | const last_dir_fd = if (last_path.sub_path.len != 0) |
| | 742 | last_path.root_dir.handle.fd |
| | 743 | else |
| | 744 | handles.items(.dir_fd)[i]; |
| | 745 | assert(last_dir_fd != -1); |
| | 746 | break :fd last_dir_fd; |
| | 747 | }; |
| | 748 | const changes = [_]posix.Kevent{ |
| | 749 | .{ |
| | 750 | .ident = @bitCast(@as(isize, dir_fd)), |
| | 751 | .filter = std.c.EVFILT.VNODE, |
| | 752 | .flags = EV.DELETE, |
| | 753 | .fflags = 0, |
| | 754 | .data = 0, |
| | 755 | .udata = i, |
| | 756 | }, |
| | 757 | .{ |
| | 758 | .ident = @bitCast(@as(isize, last_dir_fd)), |
| | 759 | .filter = std.c.EVFILT.VNODE, |
| | 760 | .flags = EV.ADD, |
| | 761 | .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE, |
| | 762 | .data = 0, |
| | 763 | .udata = i, |
| | 764 | }, |
| | 765 | }; |
| | 766 | const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes; |
| | 767 | _ = try posix.kevent(w.os.kq_fd, filtered_changes, &.{}, null); |
| | 768 | if (path.sub_path.len != 0) posix.close(dir_fd); |
| | 769 | |
| | 770 | w.dir_table.swapRemoveAt(i); |
| | 771 | handles.swapRemove(i); |
| | 772 | } |
| 716 | w.generation +%= 1; | 773 | w.generation +%= 1; |
| 717 | } | 774 | } |
| 718 | } | 775 | } |
| ... | @@ -722,7 +779,7 @@ const Os = switch (builtin.os.tag) { | ... | @@ -722,7 +779,7 @@ const Os = switch (builtin.os.tag) { |
| 722 | var event_buffer: [100]posix.Kevent = undefined; | 779 | var event_buffer: [100]posix.Kevent = undefined; |
| 723 | var n = try posix.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer)); | 780 | var n = try posix.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer)); |
| 724 | if (n == 0) return .timeout; | 781 | if (n == 0) return .timeout; |
| 725 | const reaction_sets = w.os.reaction_sets.items; | 782 | const reaction_sets = w.os.handles.items(.rs); |
| 726 | var any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], false); | 783 | var any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], false); |
| 727 | timespec_buffer = .{ .sec = 0, .nsec = 0 }; | 784 | timespec_buffer = .{ .sec = 0, .nsec = 0 }; |
| 728 | while (n == event_buffer.len) { | 785 | while (n == event_buffer.len) { |