diff options
author | Eric Wong <e@80x24.org> | 2021-01-10 12:15:02 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2021-01-12 03:51:42 +0000 |
commit | c17c44d9e0ef28f0f0521656f335f836ad8b7754 (patch) | |
tree | 32ee54ba703a76adbdb91beeba761f18a3d0884b /t | |
parent | a7e6a8cd68fb6d700337d8dbc7ee2c65ff3d2fc1 (diff) | |
download | public-inbox-c17c44d9e0ef28f0f0521656f335f836ad8b7754.tar.gz |
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.
Diffstat (limited to 't')
-rw-r--r-- | t/cmd_ipc.t | 90 | ||||
-rw-r--r-- | t/lei.t | 22 | ||||
-rw-r--r-- | t/spawn.t | 29 |
3 files changed, 105 insertions, 36 deletions
diff --git a/t/cmd_ipc.t b/t/cmd_ipc.t new file mode 100644 index 00000000..b9f4d128 --- /dev/null +++ b/t/cmd_ipc.t @@ -0,0 +1,90 @@ +#!perl -w +# Copyright (C) 2021 all contributors <meta@public-inbox.org> +# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> +use strict; +use v5.10.1; +use Test::More; +use PublicInbox::TestCommon; +use Socket qw(AF_UNIX SOCK_STREAM MSG_EOR); +pipe(my ($r, $w)) or BAIL_OUT; +my ($send, $recv); +require_ok 'PublicInbox::Spawn'; +my $SOCK_SEQPACKET = eval { Socket::SOCK_SEQPACKET() } // undef; + +my $do_test = sub { SKIP: { + my ($type, $flag, $desc) = @_; + defined $type or skip 'SOCK_SEQPACKET missing', 7; + my ($s1, $s2); + my $src = 'some payload' x 40; + socketpair($s1, $s2, AF_UNIX, $type, 0) or BAIL_OUT $!; + $send->($s1, fileno($r), fileno($w), fileno($s1), $src, $flag); + my (@fds) = $recv->($s2, my $buf, length($src) + 1); + is($buf, $src, 'got buffer payload '.$desc); + my ($r1, $w1, $s1a); + my $opens = sub { + ok(open($r1, '<&=', $fds[0]), 'opened received $r'); + ok(open($w1, '>&=', $fds[1]), 'opened received $w'); + ok(open($s1a, '+>&=', $fds[2]), 'opened received $s1'); + }; + $opens->(); + my @exp = stat $r; + my @cur = stat $r1; + is("$exp[0]\0$exp[1]", "$cur[0]\0$cur[1]", '$r dev/ino matches'); + @exp = stat $w; + @cur = stat $w1; + is("$exp[0]\0$exp[1]", "$cur[0]\0$cur[1]", '$w dev/ino matches'); + @exp = stat $s1; + @cur = stat $s1a; + is("$exp[0]\0$exp[1]", "$cur[0]\0$cur[1]", '$s1 dev/ino matches'); + if (defined($SOCK_SEQPACKET) && $type == $SOCK_SEQPACKET) { + $r1 = $w1 = $s1a = undef; + $src = (',' x 1023) . '-' .('.' x 1024); + $send->($s1, fileno($r), fileno($w), fileno($s1), $src, $flag); + (@fds) = $recv->($s2, $buf, 1024); + is($buf, (',' x 1023) . '-', 'silently truncated buf'); + $opens->(); + $r1 = $w1 = $s1a = undef; + close $s1; + @fds = $recv->($s2, $buf, length($src) + 1); + is_deeply(\@fds, [], "no FDs on EOF $desc"); + is($buf, '', "buffer cleared on EOF ($desc)"); + + } +} }; + +my $send_ic = PublicInbox::Spawn->can('send_cmd4'); +my $recv_ic = PublicInbox::Spawn->can('recv_cmd4'); +SKIP: { + ($send_ic && $recv_ic) or skip 'Inline::C not installed/enabled', 12; + $send = $send_ic; + $recv = $recv_ic; + $do_test->(SOCK_STREAM, 0, 'Inline::C stream'); + $do_test->($SOCK_SEQPACKET, MSG_EOR, 'Inline::C seqpacket'); +} + +SKIP: { + require_mods('Socket::MsgHdr', 13); + require_ok 'PublicInbox::CmdIPC4'; + $send = PublicInbox::CmdIPC4->can('send_cmd4'); + $recv = PublicInbox::CmdIPC4->can('recv_cmd4'); + $do_test->(SOCK_STREAM, 0, 'MsgHdr stream'); + $do_test->($SOCK_SEQPACKET, MSG_EOR, 'MsgHdr seqpacket'); + SKIP: { + ($send_ic && $recv_ic) or + skip 'Inline::C not installed/enabled', 12; + $recv = $recv_ic; + $do_test->(SOCK_STREAM, 0, 'Inline::C -> MsgHdr stream'); + $do_test->($SOCK_SEQPACKET, 0, 'Inline::C -> MsgHdr seqpacket'); + } +} + +SKIP: { + require_mods('IO::FDPass', 13); + require_ok 'PublicInbox::CmdIPC1'; + $send = PublicInbox::CmdIPC1->can('send_cmd1'); + $recv = PublicInbox::CmdIPC1->can('recv_cmd1'); + $do_test->(SOCK_STREAM, 0, 'IO::FDPass stream'); + $do_test->($SOCK_SEQPACKET, MSG_EOR, 'IO::FDPass seqpacket'); +} + +done_testing; @@ -10,6 +10,8 @@ use File::Path qw(rmtree); require_git 2.6; require_mods(qw(json DBD::SQLite Search::Xapian)); my $opt = { 1 => \(my $out = ''), 2 => \(my $err = '') }; +my ($home, $for_destroy) = tmpdir(); +my $err_filter; my $lei = sub { my ($cmd, $env, $xopt) = @_; $out = $err = ''; @@ -17,10 +19,12 @@ my $lei = sub { ($env, $xopt) = grep { (!defined) || ref } @_; $cmd = [ grep { defined && !ref } @_ ]; } - run_script(['lei', @$cmd], $env, $xopt // $opt); + my $res = run_script(['lei', @$cmd], $env, $xopt // $opt); + $err_filter and + $err = join('', grep(!/$err_filter/, split(/^/m, $err))); + $res; }; -my ($home, $for_destroy) = tmpdir(); delete local $ENV{XDG_DATA_HOME}; delete local $ENV{XDG_CONFIG_HOME}; local $ENV{GIT_COMMITTER_EMAIL} = 'lei@example.com'; @@ -195,18 +199,22 @@ my $test_lei_common = sub { if ($ENV{TEST_LEI_ONESHOT}) { require_ok 'PublicInbox::LEI'; - # force sun_path[108] overflow, "IO::FDPass" avoids warning - local $ENV{XDG_RUNTIME_DIR} = "$home/IO::FDPass".('.sun_path' x 108); + # force sun_path[108] overflow, ($lei->() filters out this path) + my $xrd = "$home/1shot-test".('.sun_path' x 108); + local $ENV{XDG_RUNTIME_DIR} = $xrd; + $err_filter = qr!\Q$xrd!; $test_lei_common->(); } SKIP: { # real socket require_mods(qw(Cwd), my $nr = 105); - my $nfd = eval { require IO::FDPass; 1 } // do { + my $nfd = eval { require Socket::MsgHdr; 4 } // + eval { require IO::FDPass; 1 } // do { require PublicInbox::Spawn; - PublicInbox::Spawn->can('send_3fds') ? 3 : undef; + PublicInbox::Spawn->can('send_cmd4') ? 4 : undef; } // - skip 'IO::FDPass missing or Inline::C not installed/configured', $nr; + skip 'Socket::MsgHdr, IO::FDPass or Inline::C missing or unconfigured', + $nr; local $ENV{XDG_RUNTIME_DIR} = "$home/xdg_run"; my $sock = "$ENV{XDG_RUNTIME_DIR}/lei/$nfd.sock"; @@ -5,35 +5,6 @@ use warnings; use Test::More; use PublicInbox::Spawn qw(which spawn popen_rd); use PublicInbox::Sigfd; -use Socket qw(AF_UNIX SOCK_STREAM); - -SKIP: { - my $recv_3fds = PublicInbox::Spawn->can('recv_3fds'); - my $send_3fds = PublicInbox::Spawn->can('send_3fds'); - skip 'Inline::C not enabled', 3 unless $send_3fds && $recv_3fds; - my ($s1, $s2); - socketpair($s1, $s2, AF_UNIX, SOCK_STREAM, 0) or BAIL_OUT $!; - pipe(my ($r, $w)) or BAIL_OUT $!; - my @orig = ($r, $w, $s2); - my @fd = map { fileno($_) } @orig; - ok($send_3fds->(fileno($s1), $fd[0], $fd[1], $fd[2]), - 'FDs sent'); - my (@fds) = $recv_3fds->(fileno($s2)); - is(scalar(@fds), 3, 'got 3 fds'); - use Data::Dumper; diag Dumper(\@fds); - is(scalar(grep(/\A\d+\z/, @fds)), 3, 'all valid FDs'); - my $i = 0; - my @cmp = map { - open my $new, $_, shift(@fds) or BAIL_OUT "open $! $i => $_"; - ($new, shift(@orig), $i++); - } (qw(<&= >&= +<&=)); - while (my ($new, $old, $fd) = splice(@cmp, 0, 3)) { - my @new = stat($new); - my @old = stat($old); - is("$old[0]\0$old[1]", "$new[0]\0$new[1]", - "device/inode matches on received FD:$fd"); - } -} { my $true = which('true'); |