From d6674af04cb74a4efd513d938bed8bf7ab2838eb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 27 Nov 2019 01:33:33 +0000 Subject: httpd|nntpd: avoid missed signal wakeups 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. --- t/sigfd.t | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 t/sigfd.t (limited to 't/sigfd.t') 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 +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; -- cgit v1.2.3-24-ge0c7