about summary refs log tree commit homepage
path: root/t
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2021-01-10 12:15:02 +0000
committerEric Wong <e@80x24.org>2021-01-12 03:51:42 +0000
commitc17c44d9e0ef28f0f0521656f335f836ad8b7754 (patch)
tree32ee54ba703a76adbdb91beeba761f18a3d0884b /t
parenta7e6a8cd68fb6d700337d8dbc7ee2c65ff3d2fc1 (diff)
downloadpublic-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.t90
-rw-r--r--t/lei.t22
-rw-r--r--t/spawn.t29
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;
diff --git a/t/lei.t b/t/lei.t
index 72c50308..992800a5 100644
--- a/t/lei.t
+++ b/t/lei.t
@@ -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";
diff --git a/t/spawn.t b/t/spawn.t
index 558afc28..0eed79bb 100644
--- a/t/spawn.t
+++ b/t/spawn.t
@@ -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');