]> Sergey Matveev's repositories - public-inbox.git/commitdiff
inboxidle: avoid per-inbox anonymous subs
authorEric Wong <e@yhbt.net>
Thu, 2 Jul 2020 03:32:56 +0000 (03:32 +0000)
committerEric Wong <e@yhbt.net>
Thu, 2 Jul 2020 19:51:14 +0000 (19:51 +0000)
Anonymous subs cost over 5K each on x86-64.  So prefer the
less-recommended-but-still-documented way of using
Linux::Inotify2::watch to register watchers.

This also updates FakeInotify to detect modifications correctly
when used on systems with neither IO::KQueue nor
Linux::Inotify2.

lib/PublicInbox/DirIdle.pm
lib/PublicInbox/FakeInotify.pm
lib/PublicInbox/InboxIdle.pm
lib/PublicInbox/KQNotify.pm
t/fake_inotify.t
t/kqnotify.t

index fbbc9531a20a4971c23090ed306f4d34681bd2bb..89cce305f872816d4048558859d5954b2b4c00cc 100644 (file)
@@ -23,7 +23,7 @@ if ($^O eq 'linux' && eval { require Linux::Inotify2; 1 }) {
 
 sub new {
        my ($class, $dirs, $cb) = @_;
-       my $self = bless {}, $class;
+       my $self = bless { cb => $cb }, $class;
        my $inot;
        if ($ino_cls) {
                $inot = $ino_cls->new or die "E: $ino_cls->new: $!";
@@ -35,15 +35,20 @@ sub new {
        }
 
        # Linux::Inotify2->watch or similar
-       $inot->watch($_, $MAIL_IN, $cb) for @$dirs;
+       $inot->watch($_, $MAIL_IN) for @$dirs;
        $self->{inot} = $inot;
+       PublicInbox::FakeInotify::poll_once($self) if !$ino_cls;
        $self;
 }
 
 sub event_step {
        my ($self) = @_;
-       eval { $self->{inot}->poll }; # Linux::Inotify2::poll
-       warn "$self->{inot}->poll err: $@\n" if $@;
+       my $cb = $self->{cb};
+       eval {
+               my @events = $self->{inot}->read; # Linux::Inotify2->read
+               $cb->($_) for @events;
+       };
+       warn "$self->{inot}->read err: $@\n" if $@;
 }
 
 1;
index debd2d39ae58baf08deea2198547f64f99dc743b..9275861368a0a00df5b95ff8acb4776413568d34 100644 (file)
@@ -7,71 +7,66 @@ package PublicInbox::FakeInotify;
 use strict;
 use Time::HiRes qw(stat);
 use PublicInbox::DS;
-my $IN_CLOSE = 0x08 | 0x10; # match Linux inotify
+sub IN_MODIFY () { 0x02 } # match Linux inotify
 # my $IN_MOVED_TO = 0x80;
 # my $IN_CREATE = 0x100;
 sub MOVED_TO_OR_CREATE () { 0x80 | 0x100 }
 
 my $poll_intvl = 2; # same as Filesys::Notify::Simple
 
-sub poll_once {
-       my ($self) = @_;
-       eval { $self->poll };
-       warn "E: FakeInotify->poll: $@\n" if $@;
-       PublicInbox::DS::add_timer($poll_intvl, \&poll_once, $self);
-}
-
-sub new {
-       my $self = bless { watch => {} }, __PACKAGE__;
-       PublicInbox::DS::add_timer($poll_intvl, \&poll_once, $self);
-       $self;
-}
+sub new { bless { watch => {} }, __PACKAGE__ }
 
 # behaves like Linux::Inotify2->watch
 sub watch {
-       my ($self, $path, $mask, $cb) = @_;
+       my ($self, $path, $mask) = @_;
        my @st = stat($path) or return;
        my $k = "$path\0$mask";
-       $self->{watch}->{$k} = [ $st[10], $cb ]; # 10 - ctime
+       $self->{watch}->{$k} = $st[10]; # 10 - ctime
        bless [ $self->{watch}, $k ], 'PublicInbox::FakeInotify::Watch';
 }
 
 sub on_new_files ($$$$) {
-       my ($dh, $cb, $path, $old_ctime) = @_;
+       my ($events, $dh, $path, $old_ctime) = @_;
        while (defined(my $base = readdir($dh))) {
                next if $base =~ /\A\.\.?\z/;
                my $full = "$path/$base";
                my @st = stat($full);
                if (@st && $st[10] > $old_ctime) {
-                       bless \$full, 'PublicInbox::FakeInotify::Event';
-                       eval { $cb->(\$full) };
+                       push @$events,
+                               bless(\$full, 'PublicInbox::FakeInotify::Event')
                }
        }
 }
 
-# behaves like non-blocking Linux::Inotify2->poll
-sub poll {
+# behaves like non-blocking Linux::Inotify2->read
+sub read {
        my ($self) = @_;
-       my $watch = $self->{watch} or return;
+       my $watch = $self->{watch} or return ();
+       my $events = [];
        for my $x (keys %$watch) {
                my ($path, $mask) = split(/\0/, $x, 2);
                my @now = stat($path) or next;
-               my $prv = $watch->{$x};
-               my $cb = $prv->[-1];
-               my $old_ctime = $prv->[0];
-               if ($old_ctime != $now[10]) {
-                       if (($mask & $IN_CLOSE) == $IN_CLOSE) {
-                               eval { $cb->() };
-                       } elsif ($mask & MOVED_TO_OR_CREATE) {
-                               opendir(my $dh, $path) or do {
-                                       warn "W: opendir $path: $!\n";
-                                       next;
-                               };
-                               on_new_files($dh, $cb, $path, $old_ctime);
-                       }
+               my $old_ctime = $watch->{$x};
+               $watch->{$x} = $now[10];
+               next if $old_ctime == $now[10];
+               if ($mask & IN_MODIFY) {
+                       push @$events,
+                               bless(\$path, 'PublicInbox::FakeInotify::Event')
+               } elsif ($mask & MOVED_TO_OR_CREATE) {
+                       opendir(my $dh, $path) or do {
+                               warn "W: opendir $path: $!\n";
+                               next;
+                       };
+                       on_new_files($events, $dh, $path, $old_ctime);
                }
-               @$prv = ($now[10], $cb);
        }
+       @$events;
+}
+
+sub poll_once {
+       my ($obj) = @_;
+       $obj->event_step; # PublicInbox::InboxIdle::event_step
+       PublicInbox::DS::add_timer($poll_intvl, \&poll_once, $obj);
 }
 
 package PublicInbox::FakeInotify::Watch;
@@ -82,6 +77,11 @@ sub cancel {
        delete $self->[0]->{$self->[1]};
 }
 
+sub name {
+       my ($self) = @_;
+       (split(/\0/, $self->[1], 2))[0];
+}
+
 package PublicInbox::FakeInotify::Event;
 use strict;
 
index 59cb833fd5a10b466ae74ac8a65d91df3987ffa6..bdb30284ac09b77b5bba5195514abc5458bada36 100644 (file)
@@ -36,12 +36,13 @@ sub in2_arm ($$) { # PublicInbox::Config::each_inbox callback
                $ibx->{unlock_subs} and
                        die "BUG: $dir->{unlock_subs} should not exist";
                $ibx->{unlock_subs} = $old_ibx->{unlock_subs};
-               $cur->[1]->cancel;
+               $cur->[1]->cancel; # Linux::Inotify2::Watch::cancel
        }
        $cur->[0] = $ibx;
 
        my $lock = "$dir/".($ibx->version >= 2 ? 'inbox.lock' : 'ssoma.lock');
-       $cur->[1] = $inot->watch($lock, $IN_MODIFY, sub { $ibx->on_unlock });
+       my $w = $cur->[1] = $inot->watch($lock, $IN_MODIFY);
+       $self->{on_unlock}->{$w->name} = $ibx;
 
        # TODO: detect deleted packs (and possibly other files)
 }
@@ -65,14 +66,24 @@ sub new {
        }
        $self->{inot} = $inot;
        $self->{pathmap} = {}; # inboxdir => [ ibx, watch1, watch2, watch3...]
+       $self->{on_unlock} = {}; # lock path => ibx
        refresh($self, $pi_config);
+       PublicInbox::FakeInotify::poll_once($self) if !$ino_cls;
        $self;
 }
 
 sub event_step {
        my ($self) = @_;
-       eval { $self->{inot}->poll }; # Linux::Inotify2::poll
-       warn "$self->{inot}->poll err: $@\n" if $@;
+       eval {
+               my @events = $self->{inot}->read; # Linux::Inotify2::read
+               my $on_unlock = $self->{on_unlock};
+               for my $ev (@events) {
+                       if (my $ibx = $on_unlock->{$ev->fullname}) {
+                               $ibx->on_unlock;
+                       }
+               }
+       };
+       warn "{inot}->read err: $@\n" if $@;
 }
 
 # for graceful shutdown in PublicInbox::Daemon,
index 9673b44290a991ea1853c8642c10c326c5dd779e..c7740df2dc0ca1e8dc80d12bf5af5ed4c0d3bb85 100644 (file)
@@ -19,16 +19,17 @@ sub new {
 }
 
 sub watch {
-       my ($self, $path, $mask, $cb) = @_;
-       my ($fh, $cls, @extra);
+       my ($self, $path, $mask) = @_;
+       my ($fh, $watch);
        if (-d $path) {
                opendir($fh, $path) or return;
                my @st = stat($fh);
-               @extra = ($path, $st[10]); # 10: ctime
-               $cls = 'PublicInbox::KQNotify::Watchdir';
+               $watch = bless [ $fh, $path, $st[10] ],
+                       'PublicInbox::KQNotify::Watchdir';
        } else {
                open($fh, '<', $path) or return;
-               $cls = 'PublicInbox::KQNotify::Watch';
+               $watch = bless [ $fh, $path ],
+                       'PublicInbox::KQNotify::Watch';
        }
        my $ident = fileno($fh);
        $self->{dskq}->{kq}->EV_SET($ident, # ident
@@ -37,11 +38,11 @@ sub watch {
                $mask, # fflags
                0, 0); # data, udata
        if ($mask == NOTE_WRITE || $mask == MOVED_TO_OR_CREATE) {
-               $self->{watch}->{$ident} = [ $fh, $cb, @extra ];
+               $self->{watch}->{$ident} = $watch;
        } else {
                die "TODO Not implemented: $mask";
        }
-       bless \$fh, $cls;
+       $watch;
 }
 
 # emulate Linux::Inotify::fileno
@@ -55,34 +56,41 @@ sub on_overflow {}
 # noop for Linux::Inotify2 compatibility, we use `0' timeout for ->kevent
 sub blocking {}
 
-# behave like Linux::Inotify2::poll
-sub poll {
+# behave like Linux::Inotify2->read
+sub read {
        my ($self) = @_;
        my @kevents = $self->{dskq}->{kq}->kevent(0);
+       my $events = [];
        for my $kev (@kevents) {
                my $ident = $kev->[KQ_IDENT];
                my $mask = $kev->[KQ_FFLAGS];
-               my ($dh, $cb, $path, $old_ctime) = @{$self->{watch}->{$ident}};
-               if (!defined($path) && ($mask & NOTE_WRITE) == NOTE_WRITE) {
-                       eval { $cb->() };
+               my ($dh, $path, $old_ctime) = @{$self->{watch}->{$ident}};
+               if (!defined($old_ctime)) {
+                       push @$events,
+                               bless(\$path, 'PublicInbox::FakeInotify::Event')
                } elsif ($mask & MOVED_TO_OR_CREATE) {
                        my @new_st = stat($path) or next;
                        $self->{watch}->{$ident}->[3] = $new_st[10]; # ctime
                        rewinddir($dh);
-                       PublicInbox::FakeInotify::on_new_files($dh, $cb,
+                       PublicInbox::FakeInotify::on_new_files($events, $dh,
                                                        $path, $old_ctime);
                }
        }
+       @$events;
 }
 
 package PublicInbox::KQNotify::Watch;
 use strict;
 
-sub cancel { close ${$_[0]} or die "close: $!" }
+sub name { $_[0]->[1] }
+
+sub cancel { close $_[0]->[0] or die "close: $!" }
 
 package PublicInbox::KQNotify::Watchdir;
 use strict;
 
-sub cancel { closedir ${$_[0]} or die "closedir: $!" }
+sub name { $_[0]->[1] }
+
+sub cancel { closedir $_[0]->[0] or die "closedir: $!" }
 
 1;
index f0db0cb58ec6459d41775ba53c5f5fbfee3a1a10..11dac117f0d14907eef1a3a21e0ae4a618fafadc 100644 (file)
@@ -16,29 +16,25 @@ close $fh or BAIL_OUT "close: $!";
 
 my $fi = PublicInbox::FakeInotify->new;
 my $mask = PublicInbox::FakeInotify::MOVED_TO_OR_CREATE();
-my $hit = [];
-my $cb = sub { push @$hit, map { $_->fullname } @_ };
-my $w = $fi->watch("$tmpdir/new", $mask, $cb);
+my $w = $fi->watch("$tmpdir/new", $mask);
 
 select undef, undef, undef, $MIN_FS_TICK;
 rename("$tmpdir/tst", "$tmpdir/new/tst") or BAIL_OUT "rename: $!";
-$fi->poll;
-is_deeply($hit, ["$tmpdir/new/tst"], 'rename(2) detected');
+my @events = map { $_->fullname } $fi->read;
+is_deeply(\@events, ["$tmpdir/new/tst"], 'rename(2) detected');
 
-@$hit = ();
 select undef, undef, undef, $MIN_FS_TICK;
 open $fh, '>', "$tmpdir/tst" or BAIL_OUT "open: $!";
 close $fh or BAIL_OUT "close: $!";
 link("$tmpdir/tst", "$tmpdir/new/link") or BAIL_OUT "link: $!";
-$fi->poll;
-is_deeply($hit, ["$tmpdir/new/link"], 'link(2) detected');
+@events = map { $_->fullname } $fi->read;
+is_deeply(\@events, ["$tmpdir/new/link"], 'link(2) detected');
 
 $w->cancel;
-@$hit = ();
 select undef, undef, undef, $MIN_FS_TICK;
 link("$tmpdir/new/tst", "$tmpdir/new/link2") or BAIL_OUT "link: $!";
-$fi->poll;
-is_deeply($hit, [], 'link(2) not detected after cancel');
+@events = map { $_->fullname } $fi->read;
+is_deeply(\@events, [], 'link(2) not detected after cancel');
 
 PublicInbox::DS->Reset;
 
index b3414b8ae3381f592c52b7d25495f0a47985ecf4..c3557d3e25bbcc9b6bcf90d9c2b7f263d40e2333 100644 (file)
@@ -17,25 +17,21 @@ close $fh or BAIL_OUT "close: $!";
 
 my $kqn = PublicInbox::KQNotify->new;
 my $mask = PublicInbox::KQNotify::MOVED_TO_OR_CREATE();
-my $hit = [];
-my $cb = sub { push @$hit, map { $_->fullname } @_ };
-my $w = $kqn->watch("$tmpdir/new", $mask, $cb);
+my $w = $kqn->watch("$tmpdir/new", $mask);
 
 rename("$tmpdir/tst", "$tmpdir/new/tst") or BAIL_OUT "rename: $!";
-$kqn->poll;
+my $hit = [ map { $_->fullname } $kqn->read ];
 is_deeply($hit, ["$tmpdir/new/tst"], 'rename(2) detected (via NOTE_EXTEND)');
 
-@$hit = ();
 open $fh, '>', "$tmpdir/tst" or BAIL_OUT "open: $!";
 close $fh or BAIL_OUT "close: $!";
 link("$tmpdir/tst", "$tmpdir/new/link") or BAIL_OUT "link: $!";
-$kqn->poll;
+$hit = [ grep m!/link$!, map { $_->fullname } $kqn->read ];
 is_deeply($hit, ["$tmpdir/new/link"], 'link(2) detected (via NOTE_WRITE)');
 
 $w->cancel;
-@$hit = ();
 link("$tmpdir/new/tst", "$tmpdir/new/link2") or BAIL_OUT "link: $!";
-$kqn->poll;
+$hit = [ map { $_->fullname } $kqn->read ];
 is_deeply($hit, [], 'link(2) not detected after cancel');
 
 done_testing;