From c17c44d9e0ef28f0f0521656f335f836ad8b7754 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 10 Jan 2021 12:15:02 +0000 Subject: cmd_ipc: send FDs with buffer payload For another step in in syscall reduction, we'll support transferring 3 FDs and a buffer with a single sendmsg/recvmsg syscall using Socket::MsgHdr if available. Beyond script/lei itself, this will be used for internal IPC between search backends (perhaps with SOCK_SEQPACKET). There's a chance this could make it to the public-facing daemons, too. This adds an optional dependency on the Socket::MsgHdr package, available as libsocket-msghdr-perl on Debian-based distros (but not CentOS 7.x and FreeBSD 11.x, at least). Our Inline::C version in PublicInbox::Spawn remains the last choice for script/lei due to the high startup time, and IO::FDPass remains supported for non-Debian distros. Since the socket name prefix changes from 3 to 4, we'll also take this opportunity to make the argv+env buffer transfer less error-prone by relying on argc instead of designated delimiters. --- script/lei | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) (limited to 'script') diff --git a/script/lei b/script/lei index 2ea98da4..d954b9eb 100755 --- a/script/lei +++ b/script/lei @@ -4,17 +4,20 @@ use strict; use v5.10.1; use Socket qw(AF_UNIX SOCK_STREAM pack_sockaddr_un); -my ($send_3fds, $nfd); -if (my ($sock, $pwd) = eval { - $send_3fds = eval { - require IO::FDPass; - $nfd = 1; # 1 FD per-sendmsg - sub { IO::FDPass::send($_[0], $_[$_]) for (1..3) } - } // do { - require PublicInbox::Spawn; # takes ~50ms even if built *sigh* - $nfd = 3; # 3 FDs per-sendmsg(2) - PublicInbox::Spawn->can('send_3fds'); - } // die "IO::FDPass missing or Inline::C not installed/configured\n"; +use PublicInbox::CmdIPC4; +my $narg = 4; +my $send_cmd = PublicInbox::CmdIPC4->can('send_cmd4') // do { + require PublicInbox::CmdIPC1; # 2nd choice + $narg = 1; + PublicInbox::CmdIPC1->can('send_cmd1'); +} // do { + require PublicInbox::Spawn; # takes ~50ms even if built *sigh* + $narg = 4; + PublicInbox::Spawn->can('send_cmd4'); +}; + +my ($sock, $pwd); +if ($send_cmd && eval { my $path = do { my $runtime_dir = ($ENV{XDG_RUNTIME_DIR} // '') . '/lei'; if ($runtime_dir eq '/lei') { @@ -25,29 +28,27 @@ if (my ($sock, $pwd) = eval { require File::Path; File::Path::mkpath($runtime_dir, 0, 0700); } - "$runtime_dir/$nfd.sock"; + "$runtime_dir/$narg.sock"; }; my $addr = pack_sockaddr_un($path); - socket(my $sock, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!"; + socket($sock, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!"; unless (connect($sock, $addr)) { # start the daemon if not started local $ENV{PERL5LIB} = join(':', @INC); open(my $daemon, '-|', $^X, qw[-MPublicInbox::LEI -E PublicInbox::LEI::lazy_start(@ARGV)], - $path, $! + 0, $nfd) or die "popen: $!"; + $path, $! + 0, $narg) or die "popen: $!"; while (<$daemon>) { warn $_ } # EOF when STDERR is redirected close($daemon) or warn <<""; lei-daemon could not start, exited with \$?=$? # try connecting again anyways, unlink+bind may be racy - unless (connect($sock, $addr)) { - die <<""; + connect($sock, $addr) or die <<""; connect($path): $! (after attempted daemon start) Falling back to (slow) one-shot mode - } } require Cwd; - my $pwd = $ENV{PWD} // ''; + $pwd = $ENV{PWD} // ''; my $cwd = Cwd::fastcwd() // die "fastcwd(PWD=$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): $!"; @@ -58,23 +59,21 @@ Falling back to (slow) one-shot mode } else { $pwd = $cwd; } - ($sock, $pwd); -}) { # IO::FDPass, $sock, $pwd are all available: + 1; +}) { # (Socket::MsgHdr|IO::FDPass|Inline::C), $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" } + my $buf = join("\0", $$, scalar(@ARGV), @ARGV); + while (my ($k, $v) = each %ENV) { $buf .= "\0$k=$v" } $buf .= "\0\0"; select $sock; $| = 1; # unbuffer selected $sock - $send_3fds->(fileno($sock), 0, 1, 2); - print $sock $buf or die "print(sock, buf): $!"; + $send_cmd->($sock, 0, 1, 2, $buf, 0); while ($buf = <$sock>) { $buf =~ /\Aexit=([0-9]+)\n\z/ and exit($1 + 0); 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; +} else { # for systems lacking Socket::MsgHdr, IO::FDPass or Inline::C + warn $@ if $@; require PublicInbox::LEI; PublicInbox::LEI::oneshot(__PACKAGE__); } -- cgit v1.2.3-24-ge0c7