* awaitpid (waitpid wrapper) support for reaping dead children
-* reliable signal wakeups are supported via signalfd on Linux,
- EVFILT_SIGNAL on *BSDs via IO::KQueue.
+* reliable signal wakeups are supported via epoll_pwait on Linux,
+ EVFILT_SIGNAL on *BSDs via IO::KQueue (signalfd was used in
+ public-inbox <= 1.9)
Removed features
t/select.t
t/sha.t
t/shared_kv.t
-t/sigfd.t
t/solve/0001-simple-mod.patch
t/solve/0002-rename-with-modifications.patch
t/solve/bare.patch
#ifdef __linux__
D(SYS_epoll_create1);
D(SYS_epoll_ctl);
- MAYBE D(SYS_epoll_wait);
- D(SYS_epoll_pwait);
- D(SYS_signalfd4);
+ MAYBE D(SYS_epoll_pwait);
X(IN_CLOEXEC);
X(IN_ACCESS);
use v5.10.1;
use parent qw(Exporter);
use bytes qw(length substr); # FIXME(?): needed for PublicInbox::NNTP
-use POSIX qw(WNOHANG sigprocmask SIG_SETMASK SIG_UNBLOCK);
+use POSIX qw(WNOHANG sigprocmask SIG_SETMASK);
use Fcntl qw(SEEK_SET :DEFAULT);
use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
use Scalar::Util qw(blessed);
=cut
sub Reset {
- $Poller = bless [], 'PublicInbox::DummyPoller';
+ $Poller = undef;
do {
$in_loop = undef; # first in case DESTROY callbacks use this
# clobbering $Poller may call DSKQXS::DESTROY,
$reap_armed = undef;
$loop_timeout = -1; # no timeout by default
- $Poller = PublicInbox::Select->new;
}
sub _add_named_timer {
$ret;
}
-sub allowset ($) { sigset_prep $_[0], 'fillset', 'delset' }
-sub unblockset ($) { sigset_prep $_[0], 'emptyset', 'addset' }
+sub allowset (@) {
+ my $ret = POSIX::SigSet->new;
+ $ret->fillset or die "fillset: $!";
+ for (@_) {
+ my $num = $SIGNUM{$_} // POSIX->can("SIG$_")->();
+ $ret->delset($num) or die "delset ($_ => $num): $!";
+ }
+ for (@UNBLOCKABLE) { $ret->delset($_) or die "delset ($_): $!" }
+ $ret;
+}
sub allow_sigs (@) {
- my @signames = @_;
- my $tmp = allowset(\@signames);
+ my $tmp = allowset @_;
sig_setmask($tmp, my $old = POSIX::SigSet->new);
on_destroy \&sig_setmask, $old;
}
sub event_loop (;$$) {
my ($sig, $oldset) = @_;
$Poller //= _InitPoller();
- require PublicInbox::Sigfd if $sig;
- my $sigfd = $sig ? PublicInbox::Sigfd->new($sig) : undef;
local $SIG{PIPE} = 'IGNORE';
local @SIG{keys %$sig} = values(%$sig) if $sig;
- if ($sigfd && $sigfd->{kq_sigs}) {
- # Unlike Linux signalfd, EVFILT_SIGNAL can't handle
- # signals received before the filter is created,
- # so we peek at signals here.
- my $restore = allow_sigs keys %$sig;
- select undef, undef, undef, 0; # check sigs
- }
- if (!$sigfd && $sig) {
- # wake up every second to accept signals if we don't
- # have signalfd or IO::KQueue:
- sig_setmask($oldset) if $oldset;
- sigprocmask(SIG_UNBLOCK, unblockset($sig)) or
- die "SIG_UNBLOCK: $!";
- $loop_timeout = 1000;
- }
- $_[0] = $sigfd = $sig = undef; # $_[0] == sig
+ $Poller->prepare_signals($sig, $oldset) if $sig;
+ $_[0] = $sig = undef; # $_[0] == sig
local $in_loop = 1;
do {
my $timeout = RunTimers();
# grab whatever FDs are ready
- $Poller->ep_wait($timeout, \@active);
+ $Poller->ep_wait($timeout, \@active, $oldset);
# map all FDs to their associated Perl object
@active = @FD_MAP[@active];
delete $self->{wbuf};
$FD_MAP[fileno($sock)] = undef;
- !$Poller->ep_del($sock); # stop getting notifications
+ $Poller ? !$Poller->ep_del($sock) : 1; # stop getting notifications
}
# portable, non-thread-safe sendfile emulation (no pread, yet)
sub epwait ($$) {
my ($io, $ev) = @_;
- $Poller->ep_mod($io, $ev) and croak("EPOLL_CTL_MOD($io): $!");
+ $Poller and $Poller->ep_mod($io, $ev) and croak "EPOLL_CTL_MOD $io: $!";
}
# returns 1 if done, 0 if incomplete
$pid;
}
-package PublicInbox::DummyPoller; # only used during Reset
-use v5.12;
-
-sub ep_del {}
-no warnings 'once';
-*ep_add = \&ep_del;
-*ep_mod = \&ep_del;
-
1;
=head1 AUTHORS (Danga::Socket)
# like epoll to simplify the code in DS.pm. This is NOT meant to be
# an all encompassing emulation of epoll via IO::KQueue, but just to
# support cases public-inbox-nntpd/httpd care about.
-#
-# It also implements signalfd(2) emulation via "tie".
package PublicInbox::DSKQXS;
use v5.12;
use Symbol qw(gensym);
use IO::KQueue;
use Errno qw(EAGAIN);
-use PublicInbox::OnDestroy;
-use PublicInbox::Syscall qw(EPOLLONESHOT EPOLLIN EPOLLOUT EPOLLET);
+use PublicInbox::Syscall qw(EPOLLONESHOT EPOLLIN EPOLLOUT EPOLLET %SIGNUM);
+use POSIX ();
sub EV_DISPATCH () { 0x0080 }
bless { kq => IO::KQueue->new, fgen => $fgen }, $class;
}
-# returns a new instance which behaves like signalfd on Linux.
-# It's wasteful in that it uses another FD, but it simplifies
-# our epoll-oriented code.
-sub signalfd {
- my ($class, $signo) = @_;
- my $sym = gensym;
- tie *$sym, $class, $signo; # calls TIEHANDLE
- $sym
-}
-
-sub TIEHANDLE { # similar to signalfd()
- my ($class, $signo) = @_;
- my $self = $class->new;
- my $kq = $self->{kq};
- $kq->EV_SET($_, EVFILT_SIGNAL, EV_ADD) for @$signo;
- $self;
-}
-
-sub READ { # called by sysread() for signalfd compatibility
- my ($self, undef, $len, $off) = @_; # $_[1] = buf
- die "bad args for signalfd read" if ($len % 128) // defined($off);
- my $sigbuf = $self->{sigbuf} //= [];
- my $nr = $len / 128;
- my $r = 0;
- $_[1] = '';
- while (1) {
- while ($nr--) {
- my $signo = shift(@$sigbuf) or last;
- # caller only cares about signalfd_siginfo.ssi_signo:
- $_[1] .= pack('L', $signo) . ("\0" x 124);
- $r += 128;
- }
- return $r if $r;
- my @events = eval { $self->{kq}->kevent(0) };
- # workaround https://rt.cpan.org/Ticket/Display.html?id=116615
- if ($@) {
- next if $@ =~ /Interrupted system call/;
- die;
- }
- if (!scalar(@events)) {
- $! = EAGAIN;
- return;
- }
-
- # Grab the kevent.ident (signal number). The kevent.data
- # field shows coalesced signals, and maybe we'll use it
- # in the future...
- @$sigbuf = map { $_->[0] } @events;
- }
-}
-
-# for fileno() calls in PublicInbox::DS
-sub FILENO { ${$_[0]->{kq}} }
-
sub _ep_mod_add ($$$$) {
my ($kq, $fd, $ev, $add) = @_;
$kq->EV_SET($fd, EVFILT_READ, $add|kq_flag(EPOLLIN, $ev));
- # we call this blindly for read-only FDs such as tied
- # DSKQXS (signalfd emulation) and Listeners
+ # we call this blindly for read-only FDs
eval { $kq->EV_SET($fd, EVFILT_WRITE, $add|kq_flag(EPOLLOUT, $ev)) };
0;
}
0;
}
+# there's nothing like the sigmask arg for pselect/ppoll/epoll_pwait, we
+# use EVFILT_SIGNAL to allow certain signals to wake us up from kevent
+# but let Perl invoke %SIG handlers (see $peek_sigs in ep_wait)
+sub prepare_signals {
+ my ($self, $sig, $sigset) = @_; # $sig => \%SIG like hashmap
+ my $kq = $self->{kq};
+ for (keys %$sig) {
+ my $num = $SIGNUM{$_} // POSIX->can("SIG$_")->();
+ $kq->EV_SET($num, EVFILT_SIGNAL, EV_ADD);
+ }
+ # Unlike Linux signalfd, EVFILT_SIGNAL can't handle
+ # signals received before the filter is created,
+ # so we peek at signals here:
+ my $restore = PublicInbox::DS::allow_sigs(keys %$sig);
+ select undef, undef, undef, 0; # check sigs
+ # $restore (on_destroy fires)
+}
+
sub ep_wait {
- my ($self, $timeout_msec, $events) = @_;
+ my ($self, $timeout_msec, $events, $sigmask) = @_;
# n.b.: IO::KQueue is hard-coded to return up to 1000 events
+ my $peek_sigs;
@$events = eval { $self->{kq}->kevent($timeout_msec) };
if (my $err = $@) {
# workaround https://rt.cpan.org/Ticket/Display.html?id=116615
if ($err =~ /Interrupted system call/) {
+ $peek_sigs = 1;
@$events = ();
} else {
die $err;
}
}
# caller only cares for $events[$i]->[0]
- $_ = $_->[0] for @$events;
+ @$events = map {
+ if ($_->[KQ_FILTER] == EVFILT_SIGNAL) {
+ $peek_sigs = 1;
+ ()
+ } else {
+ $_->[0]
+ }
+ } @$events;
+ if ($peek_sigs && $sigmask) {
+ my $orig = POSIX::SigSet->new;
+ PublicInbox::DS::sig_setmask($sigmask, $orig);
+ select undef, undef, undef, 0; # Perl invokes %SIG handlers here
+ PublicInbox::DS::sig_setmask($orig);
+ }
+ @$events;
}
# kqueue is close-on-fork (not exec), so we must not close it
push(@pset, $fd, $pevents);
}
@$events = ();
+ # no ppoll(2), wake up every 1s to let Perl dispatch %SIG handlers
+ $timeout_msec = 1000 if $timeout_msec < 0 || $timeout_msec > 1000;
$n = IO::Poll::_poll($timeout_msec, @pset) or return; # timeout expired
return if $n < 0 && $! == Errno::EINTR; # caller recalculates timeout
die "poll: $!" if $n < 0;
no warnings 'once';
*ep_mod = \&ep_add;
+*prepare_signals = \&PublicInbox::Select::prepare_signals;
1;
if ($pid == 0) {
undef %WORKERS;
undef $xh_workers;
- local $PublicInbox::DS::Poller; # allow epoll/kqueue
$set_user->() if $set_user;
PublicInbox::EOFpipe->new($parent_pipe, \&worker_quit);
worker_loop();
$WORKER_SIG{USR2} = sub { worker_quit() if upgrade() };
$refresh->();
}
- local $PublicInbox::DS::Poller; # allow epoll/kqueue
worker_loop();
}
# OO API for epoll
package PublicInbox::Epoll;
use v5.12;
-use PublicInbox::Syscall qw(epoll_create epoll_ctl epoll_wait
- EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL);
+use PublicInbox::Syscall qw(epoll_create epoll_ctl epoll_pwait
+ EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL %SIGNUM);
use Fcntl qw(F_SETFD FD_CLOEXEC);
+use POSIX ();
use autodie qw(open fcntl);
sub new {
# n.b. maxevents=1000 is the historical default. maxevents=1 (yes, one)
# is more fair under load with multiple worker processes sharing one listener
-sub ep_wait { epoll_wait(fileno(${$_[0]}), 1000, @_[1, 2]) }
+# ($self, \@events, $timeout, $sigset) = @_;
+sub ep_wait { epoll_pwait(fileno(${$_[0]}), 1000, @_[1..3]) }
+
+# prepare sigset for epoll_pwait
+sub prepare_signals {
+ my (undef, $sig, $sigset) = @_;
+ for (keys %$sig) { # like %SIG
+ my $num = $SIGNUM{$_} // POSIX->can("SIG$_")->();
+ $sigset->delset($num);
+ }
+}
1;
# via select, but only to support cases we care about.
package PublicInbox::Select;
use v5.12;
-use PublicInbox::Syscall qw(EPOLLONESHOT EPOLLIN EPOLLOUT);
+use PublicInbox::Syscall qw(EPOLLONESHOT EPOLLIN EPOLLOUT %SIGNUM);
use Errno;
+use POSIX ();
sub new { bless {}, __PACKAGE__ } # fd => events
vec($wvec, $fd, 1) = 1 if $ev & EPOLLOUT;
}
@$events = ();
- my $to = $msec < 0 ? undef : ($msec/1000);
+ # no pselect(2), wake up every 1s to let Perl dispatch %SIG handlers
+ my $to = $msec < 0 ? 1 : ($msec/1000);
+ $to = 1 if $to > 1;
my $n = select $rvec, $wvec, undef, $to or return; # timeout expired
return if $n < 0 && $! == Errno::EINTR; # caller recalculates timeout
die "select: $!" if $n < 0;
sub ep_del { delete($_[0]->{fileno($_[1])}); 0 }
sub ep_add { $_[0]->{fileno($_[1])} = $_[2]; 0 }
+sub prepare_signals {
+ my ($self, $sig, $sigset) = @_;
+ for (keys %$sig) { # like %SIG
+ my $num = $SIGNUM{$_} // POSIX->can("SIG$_")->();
+ $sigset->delset($num);
+ }
+ PublicInbox::DS::sig_setmask($sigset);
+}
+
no warnings 'once';
*ep_mod = \&ep_add;
-# Copyright (C) all contributors <meta@public-inbox.org>
-# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-
-# Wraps a signalfd (or similar) for PublicInbox::DS
-# fields: (sig: hashref similar to %SIG, but signal numbers as keys)
-package PublicInbox::Sigfd;
-use v5.12;
-use parent qw(PublicInbox::DS);
-use PublicInbox::Syscall qw(signalfd EPOLLIN EPOLLET %SIGNUM);
-use POSIX ();
-use autodie qw(kill open);
-my @num2name;
-
-# returns a coderef to unblock signals if neither signalfd or kqueue
-# are available.
-sub new {
- my ($class, $sig) = @_;
- my @signo;
- for my $name (keys %$sig) {
- my $num = $SIGNUM{$name} // POSIX->can("SIG$name")->();
- push @signo, $num;
- $num2name[$num] //= $name;
- }
- my $self = bless {}, $class;
- my $io;
- my $fd = signalfd(\@signo);
- if (defined $fd && $fd >= 0) {
- open $io, '+<&=', $fd;
- } elsif (eval { require PublicInbox::DSKQXS }) {
- $io = PublicInbox::DSKQXS->signalfd(\@signo);
- $self->{kq_sigs} = [ keys %$sig ];
- } else {
- return; # wake up every second to check for signals
- }
- $self->SUPER::new($io, EPOLLIN | EPOLLET);
- $self;
-}
-
-# PublicInbox::Daemon in master main loop (blocking)
-sub wait_once ($) {
- my ($self) = @_;
- # 128 == sizeof(struct signalfd_siginfo)
- my $r = sysread($self->{sock}, my $buf, 128 * 64);
- if ($self->{kq_sigs}) {
- # kqueue doesn't consume signals the same way signalfd does,
- # so the OS + Perl can make calls for us:
- my $restore = PublicInbox::DS::allow_sigs @{$self->{kq_sigs}};
- select undef, undef, undef, 0; # checks signals
- } elsif (defined($r)) { # Linux signalfd
- my $nr = $r / 128 - 1; # $nr may be -1
- for my $off (0..$nr) {
- # the first uint32_t of signalfd_siginfo: ssi_signo
- my $num = unpack('L', substr($buf, 128 * $off, 4));
- my $name = $num2name[$num];
- my $cb = $SIG{$name} || 'IGNORE';
- if ($cb eq 'DEFAULT') {
- my $restore = PublicInbox::DS::allow_sigs $name;
- kill $name, $$;
- select undef, undef, undef, 0; # checks signals
- # $restore fires
- } elsif (ref $cb) {
- $cb->($name);
- } # undef
- }
- }
- $r;
-}
-
-# called by PublicInbox::DS in epoll_wait loop
-sub event_step {
- while (wait_once($_[0])) {} # non-blocking
-}
-
-1;
use List::Util qw(sum);
# $VERSION = '0.25'; # Sys::Syscall version
-our @EXPORT_OK = qw(epoll_ctl epoll_create epoll_wait
+our @EXPORT_OK = qw(epoll_create
EPOLLIN EPOLLOUT EPOLLET
EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD
EPOLLONESHOT EPOLLEXCLUSIVE
- signalfd rename_noreplace %SIGNUM $F_SETPIPE_SZ);
+ rename_noreplace %SIGNUM $F_SETPIPE_SZ);
use constant {
EPOLLIN => 1,
EPOLLOUT => 4,
our ($SYS_epoll_create,
$SYS_epoll_ctl,
- $SYS_epoll_wait,
- $SYS_signalfd4,
+ $SYS_epoll_pwait,
$SYS_renameat2,
$F_SETPIPE_SZ,
$SYS_sendmsg,
if ($^O eq "linux") {
$F_SETPIPE_SZ = 1031;
my (undef, undef, $release, undef, $machine) = POSIX::uname();
- my ($maj, $min) = ($release =~ /\A([0-9]+)\.([0-9]+)/);
- $SYS_renameat2 = 0 if "$maj.$min" < 3.15;
+ my ($kver) = ($release =~ /([0-9]+(?:\.(?:[0-9]+))+)/);
+ $kver = eval("v$kver") // v2.6;
+ $SYS_renameat2 = 0 if $kver lt v3.15;
+ $SYS_epoll_pwait = 0 if $kver lt v2.6.19;
# whether the machine requires 64-bit numbers to be on 8-byte
# boundaries.
my $u64_mod_8 = 0;
if ($machine =~ m/^i[3456]86$/) {
$SYS_epoll_create = 254;
$SYS_epoll_ctl = 255;
- $SYS_epoll_wait = 256;
- $SYS_signalfd4 = 327;
+ $SYS_epoll_pwait //= 319;
$SYS_renameat2 //= 353;
$SYS_fstatfs = 100;
$SYS_sendmsg = 370;
} elsif ($machine eq "x86_64") {
$SYS_epoll_create = 213;
$SYS_epoll_ctl = 233;
- $SYS_epoll_wait = 232;
- $SYS_signalfd4 = 289;
+ $SYS_epoll_pwait //= 281;
$SYS_renameat2 //= 316;
$SYS_fstatfs = 138;
$SYS_sendmsg = 46;
} elsif ($machine eq 'x32') {
$SYS_epoll_create = 1073742037;
$SYS_epoll_ctl = 1073742057;
- $SYS_epoll_wait = 1073742056;
- $SYS_signalfd4 = 1073742113;
+ $SYS_epoll_pwait //= 0x40000000 + 281;
$SYS_renameat2 //= 0x40000000 + 316;
$SYS_fstatfs = 138;
$SYS_sendmsg = 0x40000206;
} elsif ($machine eq 'sparc64') {
$SYS_epoll_create = 193;
$SYS_epoll_ctl = 194;
- $SYS_epoll_wait = 195;
+ $SYS_epoll_pwait //= 309;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 317;
$SYS_renameat2 //= 345;
$SFD_CLOEXEC = 020000000;
$SYS_fstatfs = 158;
} elsif ($machine =~ m/^parisc/) { # untested, no machine on cfarm
$SYS_epoll_create = 224;
$SYS_epoll_ctl = 225;
- $SYS_epoll_wait = 226;
+ $SYS_epoll_pwait //= 297;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 309;
$SIGNUM{WINCH} = 23;
} elsif ($machine =~ m/^ppc64/) {
$SYS_epoll_create = 236;
$SYS_epoll_ctl = 237;
- $SYS_epoll_wait = 238;
+ $SYS_epoll_pwait //= 303;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 313;
$SYS_renameat2 //= 357;
$SYS_fstatfs = 100;
$SYS_sendmsg = 341;
} elsif ($machine eq "ppc") { # untested, no machine on cfarm
$SYS_epoll_create = 236;
$SYS_epoll_ctl = 237;
- $SYS_epoll_wait = 238;
+ $SYS_epoll_pwait //= 303;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 313;
$SYS_renameat2 //= 357;
$SYS_fstatfs = 100;
$SYS_writev = 146;
} elsif ($machine =~ m/^s390/) { # untested, no machine on cfarm
$SYS_epoll_create = 249;
$SYS_epoll_ctl = 250;
- $SYS_epoll_wait = 251;
+ $SYS_epoll_pwait //= 312;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 322;
$SYS_renameat2 //= 347;
$SYS_fstatfs = 100;
$SYS_sendmsg = 370;
} elsif ($machine eq 'ia64') { # untested, no machine on cfarm
$SYS_epoll_create = 1243;
$SYS_epoll_ctl = 1244;
- $SYS_epoll_wait = 1245;
+ $SYS_epoll_pwait //= 1024 + 281;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 289;
} elsif ($machine eq "alpha") { # untested, no machine on cfarm
# natural alignment, ints are 32-bits
$SYS_epoll_create = 407;
$SYS_epoll_ctl = 408;
- $SYS_epoll_wait = 409;
+ $SYS_epoll_pwait = 474;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 484;
$SFD_CLOEXEC = 010000000;
} elsif ($machine =~ /\A(?:loong|a)arch64\z/ || $machine eq 'riscv64') {
$SYS_epoll_create = 20; # (sys_epoll_create1)
$SYS_epoll_ctl = 21;
- $SYS_epoll_wait = 22; # (sys_epoll_pwait)
+ $SYS_epoll_pwait //= 22;
$u64_mod_8 = 1;
$no_deprecated = 1;
- $SYS_signalfd4 = 74;
$SYS_renameat2 //= 276;
$SYS_fstatfs = 44;
$SYS_sendmsg = 211;
} elsif ($machine =~ m/arm(v\d+)?.*l/) { # ARM OABI (untested on cfarm)
$SYS_epoll_create = 250;
$SYS_epoll_ctl = 251;
- $SYS_epoll_wait = 252;
+ $SYS_epoll_pwait //= 346;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 355;
$SYS_renameat2 //= 382;
$SYS_fstatfs = 100;
$SYS_sendmsg = 296;
} elsif ($machine =~ m/^mips64/) { # cfarm only has 32-bit userspace
$SYS_epoll_create = 5207;
$SYS_epoll_ctl = 5208;
- $SYS_epoll_wait = 5209;
+ $SYS_epoll_pwait //= 5272;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 5283;
$SYS_renameat2 //= 5311;
$SYS_fstatfs = 5135;
$SYS_sendmsg = 5045;
} elsif ($machine =~ m/^mips/) { # 32-bit, tested on mips64 cfarm host
$SYS_epoll_create = 4248;
$SYS_epoll_ctl = 4249;
- $SYS_epoll_wait = 4250;
+ $SYS_epoll_pwait //= 4313;
$u64_mod_8 = 1;
- $SYS_signalfd4 = 4324;
$SYS_renameat2 //= 4351;
$SYS_fstatfs = 4100;
$SYS_sendmsg = 4179;
Send the output of ./devel/sysdefs-list to meta\@public-inbox.org
EOM
}
- if ($u64_mod_8) {
- *epoll_wait = \&epoll_wait_mod8;
- *epoll_ctl = \&epoll_ctl_mod8;
- } else {
- *epoll_wait = \&epoll_wait_mod4;
- *epoll_ctl = \&epoll_ctl_mod4;
+ if ($SYS_epoll_pwait) {
+ if ($u64_mod_8) {
+ *epoll_pwait = \&epoll_pwait_mod8;
+ *epoll_ctl = \&epoll_ctl_mod8;
+ } else {
+ *epoll_pwait = \&epoll_pwait_mod4;
+ *epoll_ctl = \&epoll_ctl_mod4;
+ }
+ push @EXPORT_OK, qw(epoll_pwait epoll_ctl);
}
} elsif ($^O =~ /\A(?:freebsd|openbsd|netbsd|dragonfly)\z/) {
# don't use syscall.ph here, name => number mappings are not stable on *BSD
$CONST{TMPL_msghdr} //= undef;
$CONST{MSG_MORE} //= 0;
$CONST{FIONREAD} //= undef;
+ # $Config{sig_count} is NSIG, so this is NSIG/8:
}
+my $SIGSET_SIZE = int($Config{sig_count}/8);
# SFD_CLOEXEC is arch-dependent, so IN_CLOEXEC may be, too
$INOTIFY->{IN_CLOEXEC} //= 0x80000 if $INOTIFY;
pack("LLLL", $_[3], 0, $_[2], 0));
}
-# epoll_wait wrapper
-# ARGS: (epfd, maxevents, timeout (milliseconds), arrayref)
+# epoll_pwait wrapper
+# ARGS: (epfd, maxevents, timeout (milliseconds), arrayref, sigmask)
# arrayref: values modified to be [$fd, $event]
-our $epoll_wait_events = '';
-our $epoll_wait_size = 0;
-sub epoll_wait_mod4 {
- my ($epfd, $maxevents, $timeout_msec, $events) = @_;
+our $epoll_pwait_events = '';
+our $epoll_pwait_size = 0;
+sub epoll_pwait_mod4 {
+ my ($epfd, $maxevents, $timeout_msec, $events, $oldset) = @_;
# resize our static buffer if maxevents bigger than we've ever done
- if ($maxevents > $epoll_wait_size) {
- $epoll_wait_size = $maxevents;
- vec($epoll_wait_events, $maxevents * 12 - 1, 8) = 0;
+ if ($maxevents > $epoll_pwait_size) {
+ $epoll_pwait_size = $maxevents;
+ vec($epoll_pwait_events, $maxevents * 12 - 1, 8) = 0;
}
@$events = ();
- my $ct = syscall($SYS_epoll_wait, $epfd, $epoll_wait_events,
- $maxevents, $timeout_msec);
+ my $ct = syscall($SYS_epoll_pwait, $epfd, $epoll_pwait_events,
+ $maxevents, $timeout_msec,
+ $oldset ? ($$oldset, $SIGSET_SIZE) : (undef, 0));
for (0..$ct - 1) {
# 12-byte struct epoll_event
# 4 bytes uint32_t events mask (skipped, useless to us)
# 8 bytes: epoll_data_t union (first 4 bytes are the fd)
# So we skip the first 4 bytes and take the middle 4:
- $events->[$_] = unpack('L', substr($epoll_wait_events,
+ $events->[$_] = unpack('L', substr($epoll_pwait_events,
12 * $_ + 4, 4));
}
}
-sub epoll_wait_mod8 {
- my ($epfd, $maxevents, $timeout_msec, $events) = @_;
+sub epoll_pwait_mod8 {
+ my ($epfd, $maxevents, $timeout_msec, $events, $oldset) = @_;
# resize our static buffer if maxevents bigger than we've ever done
- if ($maxevents > $epoll_wait_size) {
- $epoll_wait_size = $maxevents;
- vec($epoll_wait_events, $maxevents * 16 - 1, 8) = 0;
+ if ($maxevents > $epoll_pwait_size) {
+ $epoll_pwait_size = $maxevents;
+ vec($epoll_pwait_events, $maxevents * 16 - 1, 8) = 0;
}
@$events = ();
- my $ct = syscall($SYS_epoll_wait, $epfd, $epoll_wait_events,
+ my $ct = syscall($SYS_epoll_pwait, $epfd, $epoll_pwait_events,
$maxevents, $timeout_msec,
- $no_deprecated ? undef : ());
+ $oldset ? ($$oldset, $SIGSET_SIZE) : (undef, 0));
for (0..$ct - 1) {
# 16-byte struct epoll_event
# 4 bytes uint32_t events mask (skipped, useless to us)
# 4 bytes padding (skipped, useless)
# 8 bytes epoll_data_t union (first 4 bytes are the fd)
# So skip the first 8 bytes, take 4, and ignore the last 4:
- $events->[$_] = unpack('L', substr($epoll_wait_events,
+ $events->[$_] = unpack('L', substr($epoll_pwait_events,
16 * $_ + 8, 4));
}
}
-sub signalfd ($) {
- my ($signos) = @_;
- if ($SYS_signalfd4) {
- my $set = POSIX::SigSet->new(@$signos);
- syscall($SYS_signalfd4, -1, "$$set",
- # $Config{sig_count} is NSIG, so this is NSIG/8:
- int($Config{sig_count}/8),
- # SFD_NONBLOCK == O_NONBLOCK for every architecture
- O_NONBLOCK|$SFD_CLOEXEC);
- } else {
- $! = ENOSYS;
- undef;
- }
-}
-
sub _rename_noreplace_racy ($$) {
my ($old, $new) = @_;
if (link($old, $new)) {
quit_waiter_pipe wait_for_eof require_git_http_backend
tcp_host_port test_lei lei lei_ok $lei_out $lei_err $lei_opt
test_httpd no_httpd_errors xbail require_cmd is_xdeeply tail_f
- ignore_inline_c_missing no_pollerfd no_coredump cfg_new
+ ignore_inline_c_missing no_coredump cfg_new
require_fast_reliable_signals
strace strace_inject lsof_pid oct_is $find_xh_pid);
require Test::More;
}
sub require_fast_reliable_signals (;$) {
- state $ok = do {
- require PublicInbox::Sigfd;
- my $s = PublicInbox::Sigfd->new({}) ? 1 : $ENV{TEST_UNRELIABLE};
- PublicInbox::DS->Reset;
- $s;
- };
+ state $ok = !!(PublicInbox::Syscall->can('epoll_pwait') //
+ eval { require IO::KQueue });
return $ok if $ok || defined(wantarray);
my $m = "fast, reliable signals not available(\$^O=$^O)";
@_ ? skip($m, 1) : plan(skip_all => $m);
}
}
-sub no_pollerfd ($) {
- my ($pid) = @_;
- my ($re, @cmd);
- $^O eq 'linux' and
- ($re, @cmd) = (qr/\Q[eventpoll]\E/, qw(lsof -p), $pid);
- # n.b. *BSDs uses kqueue to emulate signalfd and/or inotify,
- # and we can't distinguish which is which easily.
- SKIP: {
- (@cmd && $re) or
- skip 'open poller test is Linux-only', 1;
- my $bin = require_cmd($cmd[0], 1) or skip "$cmd[0] missing", 1;
- $cmd[0] = $bin;
- my @of = xqx(\@cmd, {}, {2 => \(my $e)});
- my $err = $?;
- skip "$bin broken? (\$?=$err) ($e)", 1 if $err;
- @of = grep /\b$pid\b/, @of; # busybox lsof ignores -p
- is(grep(/$re/, @of), 0, "no $re FDs") or diag explain(\@of);
- }
-}
-
sub cfg_new ($;@) {
my ($tmpdir, @body) = @_;
require PublicInbox::Config;
sub watch_atfork_child ($) {
my ($self) = @_;
delete @$self{qw(dir_idle pids scan_q)};
- my $sig = delete $self->{sig};
- $sig->{CHLD} = $sig->{HUP} = $sig->{USR1} = 'DEFAULT';
+ $SIG{CHLD} = $SIG{HUP} = $SIG{USR1} = 'DEFAULT';
# TERM/QUIT/INT call ->quit, which works in both parent+child
- @SIG{keys %$sig} = values %$sig;
- PublicInbox::DS::sig_setmask(PublicInbox::DS::allowset($sig));
+ PublicInbox::DS::sig_setmask(
+ PublicInbox::DS::allowset(qw(QUIT TERM INT CHLD HUP USR1)));
}
sub watch_atfork_parent ($) { _done_for_now($_[0]) }
sub quit_inprogress { !$_[0]->quit_done } # post_loop_do CB
sub watch { # main entry point
- my ($self, $sig) = @_;
- my $first_sig;
- $self->{sig} //= ($first_sig = $sig);
+ my ($self, $sig, $oldset) = @_;
my $poll = {}; # intvl_seconds => [ uri1, uri2 ]
watch_imap_init($self, $poll) if $self->{imap};
watch_nntp_init($self, $poll) if $self->{nntp};
}
watch_fs_init($self) if $self->{d_re};
local @PublicInbox::DS::post_loop_do = (\&quit_inprogress, $self);
- PublicInbox::DS::event_loop($first_sig); # calls ->event_step
+ PublicInbox::DS::event_loop($sig, $oldset); # calls ->event_step
_done_for_now($self);
}
CHLD => \&PublicInbox::DS::enqueue_reap,
USR1 => \&parent_reopen_logs,
};
- PublicInbox::DS::block_signals();
+ my $oldset = PublicInbox::DS::block_signals();
start_workers();
@PublicInbox::DS::post_loop_do = \&xh_alive;
- PublicInbox::DS::event_loop($sig);
+ PublicInbox::DS::event_loop($sig, $oldset);
}
1;
GetOptions('scan!' => \$do_scan, # undocumented, testing only
'help|h' => \(my $show_help)) or do { print STDERR $help; exit 1 };
if ($show_help) { print $help; exit 0 };
-PublicInbox::DS::block_signals();
+my $oldset = PublicInbox::DS::block_signals();
STDOUT->autoflush(1);
STDERR->autoflush(1);
local $0 = $0; # local since this script may be eval-ed
$watch->quit;
$watch = PublicInbox::Watch->new(PublicInbox::Config->new);
if ($watch) {
- $watch->{sig} = $prev->{sig}; # prevent redundant signalfd
warn "# reloaded\n";
} else {
warn("E: reloading failed\n");
CHLD => \&PublicInbox::DS::enqueue_reap,
};
$sig->{QUIT} = $sig->{TERM} = $sig->{INT} = $quit;
- local @SIG{keys %$sig} = values(%$sig); # for non-signalfd/kqueue
# --no-scan is only intended for testing atm, undocumented.
PublicInbox::DS::requeue($scan) if $do_scan;
- $watch->watch($sig) while ($watch);
+ $watch->watch($sig, $oldset) while ($watch);
}
plan skip_all => $m;
}
-if ('ensure nested kqueue works for signalfd emulation') {
- require POSIX;
- my $new = POSIX::SigSet->new(POSIX::SIGHUP());
- my $old = POSIX::SigSet->new;
- my $hup = 0;
- local $SIG{HUP} = sub { $hup++ };
- POSIX::sigprocmask(POSIX::SIG_SETMASK(), $new, $old) or die;
- my $kqs = IO::KQueue->new or die;
- $kqs->EV_SET(POSIX::SIGHUP(), IO::KQueue::EVFILT_SIGNAL(),
- IO::KQueue::EV_ADD());
- kill('HUP', $$) or die;
- my @events = $kqs->kevent(3000);
- is(scalar(@events), 1, 'got one event');
- is($events[0]->[0], POSIX::SIGHUP(), 'got SIGHUP');
- my $parent = IO::KQueue->new or die;
- my $kqfd = $$kqs;
- $parent->EV_SET($kqfd, IO::KQueue::EVFILT_READ(), IO::KQueue::EV_ADD());
- kill('HUP', $$) or die;
- @events = $parent->kevent(3000);
- is(scalar(@events), 1, 'got one event');
- is($events[0]->[0], $kqfd, 'got kqfd');
- is($hup, 0, '$SIG{HUP} did not fire');
- POSIX::sigprocmask(POSIX::SIG_SETMASK(), $old) or die;
- defined(POSIX::close($kqfd)) or die;
- defined(POSIX::close($$parent)) or die;
-}
-
local $ENV{TEST_IOPOLLER} = 'PublicInbox::DSKQXS';
require './t/ds-poll.t';
syswrite($y, '1') == 1 or die;
is($p->ep_add($x, EPOLLIN|EPOLLONESHOT), 0, 'EPOLLIN|EPOLLONESHOT add');
$p->ep_wait(-1, $events);
-is(scalar @$events, 2, 'epoll_wait has 2 ready');
+is(scalar @$events, 2, 'ep_wait has 2 ready');
my @fds = sort @$events;
my @exp = sort((fileno($r), fileno($x)));
is_deeply(\@fds, \@exp, 'got both ready FDs');
use autodie;
use PublicInbox::Syscall qw(EPOLLOUT);
plan skip_all => 'not Linux' if $^O ne 'linux';
+PublicInbox::Syscall->can('epoll_pwait') or
+ plan skip_all => 'Linux kernel too old for epoll_pwait?';
require_ok 'PublicInbox::Epoll';
my $ep = PublicInbox::Epoll->new;
pipe(my $r, my $w);
is($events[0], fileno($w), 'got expected FD');
close $w;
$ep->ep_wait(0, \@events);
-is(scalar(@events), 0, 'epoll_wait timeout');
+is(scalar(@events), 0, 'epoll_pwait timeout');
done_testing;
check_sock($unix);
ok(-s $pid_file, "$w pid file written");
my $pid = $read_pid->($pid_file);
- no_pollerfd($pid) if $w eq '-W1';
is(kill('TERM', $pid), 1, "signaled daemonized $w process");
delete $td->{-extra}; # drop tail(1) process
wait_for_eof($p0, "httpd $w quit pipe");
is($lei_err, '', 'no error from daemon-pid');
like($lei_out, qr/\A[0-9]+\n\z/s, 'pid returned') or BAIL_OUT;
chomp(my $pid = $lei_out);
- no_pollerfd($pid);
ok(kill(0, $pid), 'pid is valid');
ok(-S $sock, 'sock created');
is(-s $err_log, 0, 'nothing in errors.log');
+++ /dev/null
-#!perl -w
-# Copyright (C) all contributors <meta@public-inbox.org>
-use v5.12;
-use Test::More;
-use IO::Handle;
-use POSIX qw(:signal_h);
-use Errno qw(ENOSYS);
-require_ok 'PublicInbox::Sigfd';
-use PublicInbox::DS;
-my ($linux_sigfd, $has_sigfd);
-use autodie qw(kill);
-
-SKIP: {
- if ($^O ne 'linux' && !eval { require IO::KQueue }) {
- skip 'signalfd requires Linux or IO::KQueue to emulate', 10;
- }
-
- my $old = PublicInbox::DS::block_signals();
- my $hit = {};
- my $sig = {};
- local $SIG{USR2} = sub { $hit->{USR2}++ };
- local $SIG{HUP} = sub { $hit->{HUP}++ };
- local $SIG{TERM} = sub { $hit->{TERM}++ };
- local $SIG{INT} = sub { $hit->{INT}++ };
- local $SIG{WINCH} = sub { $hit->{WINCH}++ };
- for my $s (qw(USR2 HUP TERM INT WINCH)) {
- $sig->{$s} = sub { die "SHOULD NOT BE CALLED ($s)" }
- }
- my $PID = $$;
- kill 'USR2', $PID;
- ok(!defined($hit->{USR2}), 'no USR2 yet') or diag explain($hit);
- PublicInbox::DS->Reset;
- ok($PublicInbox::Syscall::SIGNUM{WINCH}, 'SIGWINCH number defined');
- my $sigfd = PublicInbox::Sigfd->new($sig);
- if ($sigfd) {
- $linux_sigfd = 1 if $^O eq 'linux';
- $has_sigfd = 1;
- ok($sigfd, 'Sigfd->new works');
- kill 'HUP', $PID;
- kill 'INT', $PID;
- kill 'WINCH', $PID;
- 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}, 1, "sigfd fired $s";
- }
- SKIP: {
- skip 'Linux sigfd-only behavior', 1 if !$linux_sigfd;
- is $hit->{USR2}, 1,
- 'USR2 sent before signalfd created received';
- }
- PublicInbox::DS->Reset;
- $sigfd = undef;
-
- my $nbsig = PublicInbox::Sigfd->new($sig);
- ok($nbsig, 'Sigfd->new SFD_NONBLOCK works');
- is($nbsig->wait_once, undef, 'nonblocking ->wait_once');
- ok($! == Errno::EAGAIN, 'got EAGAIN');
- kill 'HUP', $PID;
- local @PublicInbox::DS::post_loop_do = (sub {}); # loop once
- PublicInbox::DS::event_loop();
- is $hit->{HUP}, 2, 'HUP sigfd fired in event loop' or
- diag explain($hit); # sometimes fails on FreeBSD 11.x
- kill 'TERM', $PID;
- kill 'HUP', $PID;
- PublicInbox::DS::event_loop();
- PublicInbox::DS->Reset;
- is $hit->{TERM}, 1, 'TERM sigfd fired in event loop';
- is $hit->{HUP}, 3, 'HUP sigfd fired in event loop';
- ok $hit->{WINCH}, 'WINCH sigfd fired in event loop';
-
- my $restore = PublicInbox::DS::allow_sigs 'HUP';
- kill 'HUP', $PID;
- select undef, undef, undef, 0;
- is $hit->{HUP}, 4, 'HUP sigfd fired after allow_sigs';
-
- undef $restore;
- kill 'HUP', $PID;
- vec($rvec = '', fileno($nbsig->{sock}), 1) = 1;
- ok select($rvec, undef, undef, 1),
- 'select reports sigfd readiness';
- is $hit->{HUP}, 4, 'HUP not fired when sigs blocked';
- $nbsig->event_step;
- is $hit->{HUP}, 5, 'HUP fires only on ->event_step';
-
- kill 'HUP', $PID;
- is $hit->{HUP}, 5, 'HUP not fired, yet';
- $restore = PublicInbox::DS::allow_sigs 'HUP';
- select(undef, undef, undef, 0);
- is $hit->{HUP}, 6, 'HUP fires from allow_sigs';
- } else {
- skip('signalfd disabled?', 10);
- }
- PublicInbox::DS::sig_setmask($old);
-}
-
-done_testing;
EOM
# n.b. --no-scan is only intended for testing atm
my $wm = start_script([qw(-watch --no-scan)], $env);
- no_pollerfd($wm->{pid});
my $eml = eml_load('t/data/0001.patch');
$eml->header_set('Cc', $addr);
my $em = PublicInbox::Emergency->new($maildir);
my $env = { PI_CONFIG => $cfg->{-f} };
# n.b. --no-scan is only intended for testing atm
my $wm = start_script([qw(-watch --no-scan)], $env);
- no_pollerfd($wm->{pid});
my $eml = eml_load 't/data/binary.patch';
$eml->header_set('Cc', $addr);
PublicInbox::XapHelper::start('-j0')]);
($ar, my $s) = $test->($^X, qw[-w -MPublicInbox::XapHelper -e
PublicInbox::XapHelper::start('-j1')]);
- no_pollerfd($ar->{pid});
}
SKIP: {
my $cmd = eval {