about summary refs log tree commit homepage
path: root/t/sigfd.t
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2019-11-27 01:33:33 +0000
committerEric Wong <e@80x24.org>2019-11-27 10:25:43 +0000
commitd6674af04cb74a4efd513d938bed8bf7ab2838eb (patch)
tree98e1924639d6ee8be3cd0bb4d614332a0a1cbef4 /t/sigfd.t
parent1e44ee6d429b853a7a87ae58e56241c55ab8c306 (diff)
downloadpublic-inbox-d6674af04cb74a4efd513d938bed8bf7ab2838eb.tar.gz
Our attempt at using a self-pipe in signal handlers was
ineffective, since pure Perl code execution is deferred
and Perl doesn't use an internal self-pipe/eventfd.  In
retrospect, I actually prefer the simplicity of Perl in
this regard...

We can use sigprocmask() from Perl, so we can introduce
signalfd(2) and EVFILT_SIGNAL support on Linux and *BSD-based
systems, respectively.  These OS primitives allow us to avoid a
race where Perl checks for signals right before epoll_wait() or
kevent() puts the process to sleep.

The (few) systems nowadays without signalfd(2) or IO::KQueue
will now see wakeups every second to avoid missed signals.
Diffstat (limited to 't/sigfd.t')
-rw-r--r--t/sigfd.t65
1 files changed, 65 insertions, 0 deletions
diff --git a/t/sigfd.t b/t/sigfd.t
new file mode 100644
index 00000000..34f30de8
--- /dev/null
+++ b/t/sigfd.t
@@ -0,0 +1,65 @@
+# Copyright (C) 2019 all contributors <meta@public-inbox.org>
+use strict;
+use Test::More;
+use IO::Handle;
+use POSIX qw(:signal_h);
+use Errno qw(ENOSYS);
+use PublicInbox::Syscall qw(SFD_NONBLOCK);
+require_ok 'PublicInbox::Sigfd';
+
+SKIP: {
+        if ($^O ne 'linux' && !eval { require IO::KQueue }) {
+                skip 'signalfd requires Linux or IO::KQueue to emulate', 10;
+        }
+        my $new = POSIX::SigSet->new;
+        $new->fillset or die "sigfillset: $!";
+        my $old = POSIX::SigSet->new;
+        sigprocmask(SIG_SETMASK, $new, $old) or die "sigprocmask $!";
+        my $hit = {};
+        my $sig = {};
+        local $SIG{HUP} = sub { $hit->{HUP}->{normal}++ };
+        local $SIG{TERM} = sub { $hit->{TERM}->{normal}++ };
+        local $SIG{INT} = sub { $hit->{INT}->{normal}++ };
+        for my $s (qw(HUP TERM INT)) {
+                $sig->{$s} = sub { $hit->{$s}->{sigfd}++ };
+        }
+        my $sigfd = PublicInbox::Sigfd->new($sig, 0);
+        if ($sigfd) {
+                require PublicInbox::DS;
+                ok($sigfd, 'Sigfd->new works');
+                kill('HUP', $$) or die "kill $!";
+                kill('INT', $$) or die "kill $!";
+                my $fd = fileno($sigfd->{sock});
+                ok($fd >= 0, 'fileno(Sigfd->{sock}) works');
+                my $rvec = '';
+                vec($rvec, $fd, 1) = 1;
+                is(select($rvec, undef, undef, undef), 1, 'select() works');
+                ok($sigfd->wait_once, 'wait_once reported success');
+                for my $s (qw(HUP INT)) {
+                        is($hit->{$s}->{sigfd}, 1, "sigfd fired $s");
+                        is($hit->{$s}->{normal}, undef,
+                                'normal $SIG{$s} not fired');
+                }
+                $sigfd = undef;
+
+                my $nbsig = PublicInbox::Sigfd->new($sig, SFD_NONBLOCK);
+                ok($nbsig, 'Sigfd->new SFD_NONBLOCK works');
+                is($nbsig->wait_once, undef, 'nonblocking ->wait_once');
+                ok($! == Errno::EAGAIN, 'got EAGAIN');
+                kill('HUP', $$) or die "kill $!";
+                PublicInbox::DS->SetPostLoopCallback(sub {}); # loop once
+                PublicInbox::DS->EventLoop;
+                is($hit->{HUP}->{sigfd}, 2, 'HUP sigfd fired in event loop');
+                kill('TERM', $$) or die "kill $!";
+                kill('HUP', $$) or die "kill $!";
+                PublicInbox::DS->EventLoop;
+                PublicInbox::DS->Reset;
+                is($hit->{TERM}->{sigfd}, 1, 'TERM sigfd fired in event loop');
+                is($hit->{HUP}->{sigfd}, 3, 'HUP sigfd fired in event loop');
+        } else {
+                skip('signalfd disabled?', 10);
+        }
+        sigprocmask(SIG_SETMASK, $old) or die "sigprocmask $!";
+}
+
+done_testing;