| ... | ... | @@ -605,7 +605,16 @@ const Os = switch (builtin.os.tag) { |
| 605 | 605 | |
| 606 | 606 | kq_fd: i32, |
| 607 | 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 | 619 | const dir_open_flags: posix.O = f: { |
| 611 | 620 | var f: posix.O = .{ |
| ... | ... | @@ -619,6 +628,9 @@ const Os = switch (builtin.os.tag) { |
| 619 | 628 | break :f f; |
| 620 | 629 | }; |
| 621 | 630 | |
| 631 | const EV = std.c.EV; |
| 632 | const NOTE = std.c.NOTE; |
| 633 | |
| 622 | 634 | fn init() !Watch { |
| 623 | 635 | const kq_fd = try posix.kqueue(); |
| 624 | 636 | errdefer posix.close(kq_fd); |
| ... | ... | @@ -626,27 +638,29 @@ const Os = switch (builtin.os.tag) { |
| 626 | 638 | .dir_table = .{}, |
| 627 | 639 | .os = .{ |
| 628 | 640 | .kq_fd = kq_fd, |
| 629 | | .reaction_sets = .{}, |
| 641 | .handles = .empty, |
| 630 | 642 | }, |
| 631 | 643 | .generation = 0, |
| 632 | 644 | }; |
| 633 | 645 | } |
| 634 | 646 | |
| 635 | 647 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { |
| 648 | const handles = &w.os.handles; |
| 636 | 649 | for (steps) |step| { |
| 637 | 650 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { |
| 638 | 651 | const reaction_set = rs: { |
| 639 | 652 | const gop = try w.dir_table.getOrPut(gpa, path); |
| 640 | 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 | 656 | path.root_dir.handle.fd |
| 643 | 657 | else |
| 644 | 658 | posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |err| { |
| 645 | 659 | fatal("failed to open directory {}: {s}", .{ path, @errorName(err) }); |
| 646 | 660 | }; |
| 647 | | const EV = std.c.EV; |
| 648 | | const NOTE = std.c.NOTE; |
| 649 | | var changes = [1]posix.Kevent{.{ |
| 661 | // Empirically the dir has to stay open or else no events are triggered. |
| 662 | errdefer if (!skip_open_dir) posix.close(dir_fd); |
| 663 | const changes = [1]posix.Kevent{.{ |
| 650 | 664 | .ident = @bitCast(@as(isize, dir_fd)), |
| 651 | 665 | .filter = std.c.EVFILT.VNODE, |
| 652 | 666 | .flags = EV.ADD | EV.ENABLE | EV.CLEAR, |
| ... | ... | @@ -655,12 +669,16 @@ const Os = switch (builtin.os.tag) { |
| 655 | 669 | .udata = gop.index, |
| 656 | 670 | }}; |
| 657 | 671 | _ = try posix.kevent(w.os.kq_fd, &changes, &.{}, null); |
| 658 | | assert(w.os.reaction_sets.items.len == gop.index); |
| 659 | | const reaction_set = try w.os.reaction_sets.addOne(gpa); |
| 660 | | reaction_set.* = .{}; |
| 661 | | break :rs reaction_set; |
| 672 | assert(handles.len == gop.index); |
| 673 | try handles.append(gpa, .{ |
| 674 | .rs = .{}, |
| 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 | 683 | for (files.items) |basename| { |
| 666 | 684 | const gop = try reaction_set.getOrPut(gpa, basename); |
| ... | ... | @@ -672,47 +690,86 @@ const Os = switch (builtin.os.tag) { |
| 672 | 690 | |
| 673 | 691 | { |
| 674 | 692 | // Remove marks for files that are no longer inputs. |
| 675 | | //var i: usize = 0; |
| 676 | | //while (i < w.os.handle_table.entries.len) { |
| 677 | | // { |
| 678 | | // const reaction_set = &w.os.handle_table.values()[i]; |
| 679 | | // var step_set_i: usize = 0; |
| 680 | | // while (step_set_i < reaction_set.entries.len) { |
| 681 | | // const step_set = &reaction_set.values()[step_set_i]; |
| 682 | | // var dirent_i: usize = 0; |
| 683 | | // while (dirent_i < step_set.entries.len) { |
| 684 | | // const generations = step_set.values(); |
| 685 | | // if (generations[dirent_i] == w.generation) { |
| 686 | | // dirent_i += 1; |
| 687 | | // continue; |
| 688 | | // } |
| 689 | | // step_set.swapRemoveAt(dirent_i); |
| 690 | | // } |
| 691 | | // if (step_set.entries.len > 0) { |
| 692 | | // step_set_i += 1; |
| 693 | | // continue; |
| 694 | | // } |
| 695 | | // reaction_set.swapRemoveAt(step_set_i); |
| 696 | | // } |
| 697 | | // if (reaction_set.entries.len > 0) { |
| 698 | | // i += 1; |
| 699 | | // continue; |
| 700 | | // } |
| 701 | | // } |
| 702 | | |
| 703 | | // const path = w.dir_table.keys()[i]; |
| 704 | | |
| 705 | | // posix.fanotify_mark(fan_fd, .{ |
| 706 | | // .REMOVE = true, |
| 707 | | // .ONLYDIR = true, |
| 708 | | // }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| switch (err) { |
| 709 | | // error.FileNotFound => {}, // Expected, harmless. |
| 710 | | // else => |e| std.log.warn("unable to unwatch '{}': {s}", .{ path, @errorName(e) }), |
| 711 | | // }; |
| 712 | | |
| 713 | | // w.dir_table.swapRemoveAt(i); |
| 714 | | // w.os.handle_table.swapRemoveAt(i); |
| 715 | | //} |
| 693 | var i: usize = 0; |
| 694 | while (i < handles.len) { |
| 695 | { |
| 696 | const reaction_set = &handles.items(.rs)[i]; |
| 697 | var step_set_i: usize = 0; |
| 698 | while (step_set_i < reaction_set.entries.len) { |
| 699 | const step_set = &reaction_set.values()[step_set_i]; |
| 700 | var dirent_i: usize = 0; |
| 701 | while (dirent_i < step_set.entries.len) { |
| 702 | const generations = step_set.values(); |
| 703 | if (generations[dirent_i] == w.generation) { |
| 704 | dirent_i += 1; |
| 705 | continue; |
| 706 | } |
| 707 | step_set.swapRemoveAt(dirent_i); |
| 708 | } |
| 709 | if (step_set.entries.len > 0) { |
| 710 | step_set_i += 1; |
| 711 | continue; |
| 712 | } |
| 713 | reaction_set.swapRemoveAt(step_set_i); |
| 714 | } |
| 715 | if (reaction_set.entries.len > 0) { |
| 716 | i += 1; |
| 717 | continue; |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | const ref_count_ptr = &handles.items(.ref_count)[i]; |
| 722 | ref_count_ptr.* -= 1; |
| 723 | if (ref_count_ptr.* > 0) continue; |
| 724 | |
| 725 | // If the sub_path == "" then this patch has already the |
| 726 | // dir fd that we need to use as the ident to remove the |
| 727 | // event. If it was opened above with openat() then we need |
| 728 | // to access that data via the dir_fd field. |
| 729 | const path = w.dir_table.keys()[i]; |
| 730 | const dir_fd = if (path.sub_path.len == 0) |
| 731 | path.root_dir.handle.fd |
| 732 | else |
| 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 | 773 | w.generation +%= 1; |
| 717 | 774 | } |
| 718 | 775 | } |
| ... | ... | @@ -722,7 +779,7 @@ const Os = switch (builtin.os.tag) { |
| 722 | 779 | var event_buffer: [100]posix.Kevent = undefined; |
| 723 | 780 | var n = try posix.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer)); |
| 724 | 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 | 783 | var any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], false); |
| 727 | 784 | timespec_buffer = .{ .sec = 0, .nsec = 0 }; |
| 728 | 785 | while (n == event_buffer.len) { |