diff options
-rw-r--r-- | lib/PublicInbox/LEI.pm | 67 | ||||
-rwxr-xr-x | script/lei | 39 | ||||
-rw-r--r-- | t/lei.t | 31 |
3 files changed, 86 insertions, 51 deletions
diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index 95b48095..fd412324 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -12,7 +12,7 @@ use parent qw(PublicInbox::DS); use Getopt::Long (); use Socket qw(AF_UNIX SOCK_STREAM pack_sockaddr_un); use Errno qw(EAGAIN ECONNREFUSED ENOENT); -use POSIX qw(setsid); +use POSIX (); use IO::Handle (); use Sys::Syslog qw(syslog openlog); use PublicInbox::Config; @@ -584,60 +584,44 @@ sub noop {} # lei(1) calls this when it can't connect sub lazy_start { - my ($path, $err) = @_; - require IO::FDPass; # require this early so caller sees it - if ($err == ECONNREFUSED) { + my ($path, $errno) = @_; + if ($errno == ECONNREFUSED) { unlink($path) or die "unlink($path): $!"; - } elsif ($err != ENOENT) { - $! = $err; # allow interpolation to stringify in die + } elsif ($errno != ENOENT) { + $! = $errno; # allow interpolation to stringify in die die "connect($path): $!"; } umask(077) // die("umask(077): $!"); socket(my $l, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!"; bind($l, pack_sockaddr_un($path)) or die "bind($path): $!"; - listen($l, 1024) or die "listen $!"; + listen($l, 1024) or die "listen: $!"; my @st = stat($path) or die "stat($path): $!"; my $dev_ino_expect = pack('dd', $st[0], $st[1]); # dev+ino pipe(my ($eof_r, $eof_w)) or die "pipe: $!"; my $oldset = PublicInbox::Sigfd::block_signals(); - my $pid = fork // die "fork: $!"; - return if $pid; + require IO::FDPass; require PublicInbox::Listener; require PublicInbox::EOFpipe; - openlog($path, 'pid', 'user'); - local $SIG{__DIE__} = sub { - syslog('crit', "@_"); - die; # calls the default __DIE__ handler - }; - local $SIG{__WARN__} = sub { syslog('warning', "@_") }; - open(STDIN, '+<', '/dev/null') or die "redirect stdin failed: $!\n"; - open STDOUT, '>&STDIN' or die "redirect stdout failed: $!\n"; - open STDERR, '>&STDIN' or die "redirect stderr failed: $!\n"; - setsid(); - $pid = fork // die "fork: $!"; + (-p STDOUT && -p STDERR) or die "E: stdout+stderr must be pipes\n"; + open(STDIN, '+<', '/dev/null') or die "redirect stdin failed: $!"; + POSIX::setsid() > 0 or die "setsid: $!"; + my $pid = fork // die "fork: $!"; return if $pid; - $SIG{__DIE__} = 'DEFAULT'; - my $on_destroy = PublicInbox::OnDestroy->new(sub { - my ($owner_pid) = @_; - syslog('crit', "$@") if $@ && $$ == $owner_pid; - }, $$); $0 = "lei-daemon $path"; local %PATH2CFG; - $l->blocking(0); - $eof_w->blocking(0); - $eof_r->blocking(0); - my $listener = PublicInbox::Listener->new($l, \&accept_dispatch, $l); + $_->blocking(0) for ($l, $eof_r, $eof_w); + $l = PublicInbox::Listener->new($l, \&accept_dispatch, $l); my $exit_code; local $quit = sub { $exit_code //= shift; - my $tmp = $listener or exit($exit_code); + my $listener = $l or exit($exit_code); unlink($path) if defined($path); - syswrite($eof_w, '.'); - $l = $listener = $path = undef; - $tmp->close if $tmp; # DS::close + # closing eof_w triggers \&noop wakeup + $eof_w = $l = $path = undef; + $listener->close; # DS::close PublicInbox::DS->SetLoopTimeout(1000); }; - PublicInbox::EOFpipe->new($eof_r, sub {}, undef); + PublicInbox::EOFpipe->new($eof_r, \&noop, undef); my $sig = { CHLD => \&PublicInbox::DS::enqueue_reap, QUIT => $quit, @@ -682,8 +666,21 @@ sub lazy_start { } $n; # true: continue, false: stop }); + + # STDIN was redirected to /dev/null above, closing STDOUT and + # STDERR will cause the calling `lei' client process to finish + # reading <$daemon> pipe. + open STDOUT, '>&STDIN' or die "redirect stdout failed: $!"; + openlog($path, 'pid', 'user'); + local $SIG{__WARN__} = sub { syslog('warning', "@_") }; + my $owner_pid = $$; + my $on_destroy = PublicInbox::OnDestroy->new(sub { + syslog('crit', "$@") if $@ && $$ == $owner_pid; + }); + open STDERR, '>&STDIN' or die "redirect stderr failed: $!"; + # $daemon pipe to `lei' closed, main loop begins: PublicInbox::DS->EventLoop; - $@ = undef if $on_destroy; # quiet OnDestroy if we got here + @$on_destroy = (); # cancel on_destroy if we get here exit($exit_code // 0); } @@ -4,8 +4,8 @@ use strict; use v5.10.1; use Socket qw(AF_UNIX SOCK_STREAM pack_sockaddr_un); - -if (eval { require IO::FDPass; 1 }) { # use daemon to reduce load time +if (my ($sock, $pwd) = eval { + require IO::FDPass; # will try to use a daemon to reduce load time my $path = do { my $runtime_dir = ($ENV{XDG_RUNTIME_DIR} // '') . '/lei'; if ($runtime_dir eq '/lei') { @@ -21,32 +21,41 @@ if (eval { require IO::FDPass; 1 }) { # use daemon to reduce load time my $addr = pack_sockaddr_un($path); socket(my $sock, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!"; unless (connect($sock, $addr)) { # start the daemon if not started - my $err = $! + 0; - my $env = { PERL5LIB => join(':', @INC) }; my $cmd = [ $^X, qw[-MPublicInbox::LEI -E PublicInbox::LEI::lazy_start(@ARGV)], - $path, $err ]; + $path, $! + 0 ]; + my $env = { PERL5LIB => join(':', @INC) }; + pipe(my ($daemon, $w)) or die "pipe: $!"; + my $opt = { 1 => $w, 2 => $w }; require PublicInbox::Spawn; - waitpid(PublicInbox::Spawn::spawn($cmd, $env), 0); - warn "lei-daemon exited with \$?=$?\n" if $?; + my $pid = PublicInbox::Spawn::spawn($cmd, $env, $opt); + $opt = $w = undef; + while (<$daemon>) { warn $_ } # EOF when STDERR is redirected + waitpid($pid, 0) or warn <<""; +lei-daemon could not start, PID:$pid exited with \$?=$? # try connecting again anyways, unlink+bind may be racy - connect($sock, $addr) or die - "connect($path): $! (after attempted daemon start)"; + unless (connect($sock, $addr)) { + die <<""; +connect($path): $! (after attempted daemon start) +Falling back to (slow) one-shot mode + + } } require Cwd; - my $cwd = Cwd::fastcwd() // die "fastcwd: $!"; + my $cwd = Cwd::fastcwd() // die "fastcwd(PWD=".($ENV{PWD}//'').": $!"; my $pwd = $ENV{PWD} // ''; - if ($pwd eq $cwd) { # likely, all good - } elsif ($pwd) { # prefer ENV{PWD} if it's a symlink to real cwd - my @st_cwd = stat($cwd) or die "stat(cwd=$cwd): $!\n"; - my @st_pwd = stat($pwd); + if ($pwd ne $cwd) { # prefer ENV{PWD} if it's a symlink to real cwd + my @st_cwd = stat($cwd) or die "stat(cwd=$cwd): $!"; + my @st_pwd = stat($pwd); # PWD invalid, use cwd # make sure st_dev/st_ino match for {PWD} to be valid $pwd = $cwd if (!@st_pwd || $st_pwd[1] != $st_cwd[1] || $st_pwd[0] != $st_cwd[0]); } else { $pwd = $cwd; } + ($sock, $pwd); +}) { # IO::FDPass, $sock, $pwd are all available: local $ENV{PWD} = $pwd; my $buf = "$$\0\0>" . join("]\0[", @ARGV) . "\0\0>"; while (my ($k, $v) = each %ENV) { $buf .= "$k=$v\0" } @@ -60,6 +69,8 @@ if (eval { require IO::FDPass; 1 }) { # use daemon to reduce load time die $buf; } } else { # for systems lacking IO::FDPass + # don't warn about IO::FDPass since it's not commonly installed + warn $@ if $@ && index($@, 'IO::FDPass') < 0; require PublicInbox::LEI; PublicInbox::LEI::oneshot(__PACKAGE__); } @@ -127,7 +127,7 @@ my $test_lei_common = sub { my $test_lei_oneshot = $ENV{TEST_LEI_ONESHOT}; SKIP: { last SKIP if $test_lei_oneshot; - require_mods('IO::FDPass', 16); + require_mods(qw(IO::FDPass Cwd), 41); my $sock = "$ENV{XDG_RUNTIME_DIR}/lei/sock"; ok(run_script([qw(lei daemon-pid)], undef, $opt), 'daemon-pid'); @@ -188,7 +188,34 @@ SKIP: { chomp(my $new_pid = $out); ok(kill(0, $new_pid), 'new pid is running'); ok(-S $sock, 'sock exists again'); - unlink $sock or BAIL_OUT "unlink $!"; + + if ('socket inaccessible') { + chmod 0000, $sock or BAIL_OUT "chmod 0000: $!"; + $out = $err = ''; + ok(run_script([qw(lei help)], undef, $opt), + 'connect fail, one-shot fallback works'); + like($err, qr/\bconnect\(/, 'connect error noted'); + like($out, qr/^usage: /, 'help output works'); + chmod 0700, $sock or BAIL_OUT "chmod 0700: $!"; + } + if ('oneshot on cwd gone') { + my $cwd = Cwd::fastcwd() or BAIL_OUT "fastcwd: $!"; + my $d = "$home/to-be-removed"; + mkdir $d or BAIL_OUT "mkdir($d) $!"; + chdir $d or BAIL_OUT "chdir($d) $!"; + if (rmdir($d)) { + $out = $err = ''; + ok(run_script([qw(lei help)], undef, $opt), + 'cwd fail, one-shot fallback works'); + } else { + $err = "rmdir=$!"; + } + chdir $cwd or BAIL_OUT "chdir($cwd) $!"; + like($err, qr/cwd\(/, 'cwd error noted'); + like($out, qr/^usage: /, 'help output still works'); + } + + unlink $sock or BAIL_OUT "unlink($sock) $!"; for (0..100) { kill('CHLD', $new_pid) or last; tick(); |