From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 4A9141F8C6 for ; Wed, 28 Jul 2021 12:34:33 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [RFC] lei: address lifetime problems from Linux::Inotify2 Date: Wed, 28 Jul 2021 12:34:33 +0000 Message-Id: <20210728123433.30248-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: Linux::Inotify2 doesn't supply an explicit close() wrapper, and Linux::Inotify2::Event objects maintain a reference to the parent Linux::Inotify2 object, so we were leaking inotify FDs into the "lei note-event" worker processes. This delays the fork()-ing code paths into the next tick of the event loop to ensure the inotify FD is closed when $dir_idle is undef'ed in ->_lei_atfork_child. Note: I'm not sure if I want to keep this, since The correct fix would be to have a way to close inotify FDs, but the current Linux::Inotify2 API doesn't make it easy. And leaking an inotify FD isn't the worst thing in the world, since it won't hang other processes. So maybe the cure is worse than the disease, in this case. --- lib/PublicInbox/LEI.pm | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index d9fd40fd..b81f95c9 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -28,8 +28,8 @@ use Time::HiRes qw(stat); # ctime comparisons for config cache use File::Path qw(mkpath); use File::Spec; our $quit = \&CORE::exit; -our ($current_lei, $errors_log, $listener, $oldset, $dir_idle, - $recv_cmd, $send_cmd); +our ($current_lei, $errors_log, $listener, $oldset, + $dir_idle, $lne_nxt, $lne_q, $recv_cmd, $send_cmd); my $GLP = Getopt::Long::Parser->new; $GLP->configure(qw(gnu_getopt no_ignore_case auto_abbrev)); my $GLP_PASS = Getopt::Long::Parser->new; @@ -557,6 +557,8 @@ sub _lei_atfork_child { close $listener if $listener; undef $listener; undef $dir_idle; + undef $lne_nxt; + undef $lne_q; %PATH2CFG = (); $MDIR2CFGPATH = {}; eval 'no warnings; undef $PublicInbox::LeiNoteEvent::to_flush'; @@ -1153,6 +1155,21 @@ sub cfg2lei ($) { $lei; } +sub lne_task { # requeue, runs without dir_idle stuff on stack + undef $lne_nxt; + my $q = $lne_q // return; + $lne_q = undef; + + while (my ($loc, $nc, $bn, $fn, $cfg) = splice(@$q, 0, 5)) { + eval { + local %ENV = %{$cfg->{-env}}; + my $lei = cfg2lei($cfg); + $lei->dispatch('note-event', $loc, $nc, $bn, $fn); + }; + warn "E note-event $cfg->{-f}: $@\n" if $@; + } +} + sub dir_idle_handler ($) { # PublicInbox::DirIdle callback my ($ev) = @_; # Linux::Inotify2::Event or duck type my $fn = $ev->fullname; @@ -1161,14 +1178,9 @@ sub dir_idle_handler ($) { # PublicInbox::DirIdle callback $nc = '' if $ev->IN_DELETE; for my $f (keys %{$MDIR2CFGPATH->{$mdir} // {}}) { my $cfg = $PATH2CFG{$f} // next; - eval { - local %ENV = %{$cfg->{-env}}; - my $lei = cfg2lei($cfg); - $lei->dispatch('note-event', - "maildir:$mdir", $nc, $bn, $fn); - }; - warn "E note-event $f: $@\n" if $@; + push @$lne_q, "maildir:$mdir", $nc, $bn, $fn, $cfg; } + $lne_nxt //= PublicInbox::DS::requeue(\&lne_task) if $lne_q; } if ($ev->can('cancel') && ($ev->IN_IGNORE || $ev->IN_UNMOUNT)) { $ev->cancel; @@ -1261,6 +1273,7 @@ sub lazy_start { undef $sig; local $SIG{PIPE} = 'IGNORE'; require PublicInbox::DirIdle; + local ($lne_q, $lne_nxt); local $dir_idle = PublicInbox::DirIdle->new([$sock_dir], sub { # just rely on wakeup to hit PostLoopCallback set below dir_idle_handler($_[0]) if $_[0]->fullname ne $path;