* [PATCH 00/10] signal-handling and *BSD fixes @ 2023-09-04 10:35 Eric Wong 2023-09-04 10:35 ` [PATCH 01/10] ds: don't block important signals we don't use Eric Wong ` (9 more replies) 0 siblings, 10 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:35 UTC (permalink / raw) To: meta kevent EVFILT_SIGNAL behaves differently than Linux signalfd in that we can't rely on it to handle signals that were sent before the existence of the kevent filter. Thus, [5/10] opens a window before entering the event. This difference between OSes was noticed due to lei tests quickly starting read-only daemons and terminating them before signal handlers were setup properly from the lack of SCM_RIGHTS support on *BSDs lacking Inline::C. Nevertheless [9/10] improves test dependency management to ensure read-only daemons don't start all when SCM_RIGHTS support is unavailable. Patch [10/10] was originally far more aggressive in that it kept all signals blocked across execve, but that's probably unrealistic for real-world scenarios 7/10 to add TTOU/TTIN support to xap_helper was actually the first developed in this series. Lots more brewing in the portability department... Eric Wong (10): ds: don't block important signals we don't use t/sigfd: test EVFILT_SIGNAL vs signalfd differences t/sigfd: better checks related to SIGWINCH update devel/syscall-list to devel/sysdefs-list daemon: workaround pre-EVFILT_SIGNAL signals watch: ensure children can use signal handlers xap_helper: support SIGTTIN+SIGTTOU worker adjustments xap_helper.h: include signal.h for sig* functions tests: add `+SCM_RIGHTS' as a require_mods target test_common: start_script: set default signals MANIFEST | 2 +- devel/{syscall-list => sysdefs-list} | 47 ++--- lib/PublicInbox/DS.pm | 38 +++- lib/PublicInbox/IPC.pm | 2 +- lib/PublicInbox/Sigfd.pm | 3 +- lib/PublicInbox/Syscall.pm | 7 +- lib/PublicInbox/TestCommon.pm | 26 ++- lib/PublicInbox/Watch.pm | 7 +- lib/PublicInbox/XapHelper.pm | 103 +++++++--- lib/PublicInbox/xap_helper.h | 283 +++++++++++++++++++++++---- script/public-inbox-watch | 4 +- t/lei-import-nntp.t | 4 +- t/lei.t | 3 +- t/sigfd.t | 28 ++- t/xap_helper.t | 59 ++++-- 15 files changed, 489 insertions(+), 127 deletions(-) rename devel/{syscall-list => sysdefs-list} (60%) ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 01/10] ds: don't block important signals we don't use 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong @ 2023-09-04 10:35 ` Eric Wong 2023-09-04 10:35 ` [PATCH 02/10] t/sigfd: test EVFILT_SIGNAL vs signalfd differences Eric Wong ` (8 subsequent siblings) 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:35 UTC (permalink / raw) To: meta Don't block SIGABRT, SIGBUS, SIGFPE, SIGILL nor SIGSEGV since blocking them can hide real bugs in our code or 3rd-party libraries and executables. We'll also leave SIGXCPU and SIGXFSZ unblocked since users may've setup RLIMIT_CPU and RLIMIT_FSIZE, respectively. --- lib/PublicInbox/DS.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm index e89dc430..97546016 100644 --- a/lib/PublicInbox/DS.pm +++ b/lib/PublicInbox/DS.pm @@ -193,10 +193,16 @@ sub RunTimers { sub sig_setmask { sigprocmask(SIG_SETMASK, @_) or die "sigprocmask: $!" } +our @UNBLOCKABLE = map { # ensure we detect bugs, HW problems and user rlimits + my $cb = POSIX->can("SIG$_"); + my $num = $cb ? $cb->() : undef; + $num ? ($num) : (); +} qw(ABRT BUS FPE ILL SEGV XCPU XFSZ); + sub block_signals { # anything in @_ stays unblocked my $newset = POSIX::SigSet->new; $newset->fillset or die "fillset: $!"; - $newset->delset($_) for @_; + for (@_, @UNBLOCKABLE) { $newset->delset($_) or die "delset($_): $!" } my $oldset = POSIX::SigSet->new; sig_setmask($newset, $oldset); $oldset; ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 02/10] t/sigfd: test EVFILT_SIGNAL vs signalfd differences 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong 2023-09-04 10:35 ` [PATCH 01/10] ds: don't block important signals we don't use Eric Wong @ 2023-09-04 10:35 ` Eric Wong 2023-09-04 10:36 ` [PATCH 03/10] t/sigfd: better checks related to SIGWINCH Eric Wong ` (7 subsequent siblings) 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:35 UTC (permalink / raw) To: meta Verify that observed OpenBSD and FreeBSD EVFILT_SIGNAL behavior works differently than what Linux signalfd does to ease upcoming changes to PublicInbox::DS. --- t/sigfd.t | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/t/sigfd.t b/t/sigfd.t index 0070ca73..15fc818a 100644 --- a/t/sigfd.t +++ b/t/sigfd.t @@ -7,6 +7,7 @@ use POSIX qw(:signal_h); use Errno qw(ENOSYS); require_ok 'PublicInbox::Sigfd'; use PublicInbox::DS; +my ($linux_sigfd, $has_sigfd); SKIP: { if ($^O ne 'linux' && !eval { require IO::KQueue }) { @@ -16,16 +17,21 @@ SKIP: { my $old = PublicInbox::DS::block_signals(); my $hit = {}; my $sig = {}; + local $SIG{USR2} = sub { $hit->{USR2}->{normal}++ }; local $SIG{HUP} = sub { $hit->{HUP}->{normal}++ }; local $SIG{TERM} = sub { $hit->{TERM}->{normal}++ }; local $SIG{INT} = sub { $hit->{INT}->{normal}++ }; local $SIG{WINCH} = sub { $hit->{WINCH}->{normal}++ }; - for my $s (qw(HUP TERM INT WINCH)) { + for my $s (qw(USR2 HUP TERM INT WINCH)) { $sig->{$s} = sub { $hit->{$s}->{sigfd}++ }; } + kill 'USR2', $$ or die "kill $!"; + ok(!defined($hit->{USR2}), 'no USR2 yet') or diag explain($hit); PublicInbox::DS->Reset; my $sigfd = PublicInbox::Sigfd->new($sig, 0); if ($sigfd) { + $linux_sigfd = 1 if $^O eq 'linux'; + $has_sigfd = 1; ok($sigfd, 'Sigfd->new works'); kill('HUP', $$) or die "kill $!"; kill('INT', $$) or die "kill $!"; @@ -39,8 +45,14 @@ SKIP: { for my $s (qw(HUP INT)) { is($hit->{$s}->{sigfd}, 1, "sigfd fired $s"); is($hit->{$s}->{normal}, undef, - 'normal $SIG{$s} not fired'); + "normal \$SIG{$s} not fired"); } + SKIP: { + skip 'Linux sigfd-only behavior', 1 if !$linux_sigfd; + is($hit->{USR2}->{sigfd}, 1, + 'USR2 sent before signalfd created received'); + } + ok(!$hit->{USR2}->{normal}, 'USR2 not fired normally'); PublicInbox::DS->Reset; $sigfd = undef; @@ -63,7 +75,14 @@ SKIP: { } else { skip('signalfd disabled?', 10); } - sigprocmask(SIG_SETMASK, $old) or die "sigprocmask $!"; + ok(!$hit->{USR2}->{normal}, 'USR2 still not fired normally'); + PublicInbox::DS::sig_setmask($old); + SKIP: { + ($has_sigfd && !$linux_sigfd) or + skip 'EVFILT_SIGNAL-only behavior check', 1; + is($hit->{USR2}->{normal}, 1, + "USR2 fired normally after unblocking on $^O"); + } } done_testing; ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 03/10] t/sigfd: better checks related to SIGWINCH 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong 2023-09-04 10:35 ` [PATCH 01/10] ds: don't block important signals we don't use Eric Wong 2023-09-04 10:35 ` [PATCH 02/10] t/sigfd: test EVFILT_SIGNAL vs signalfd differences Eric Wong @ 2023-09-04 10:36 ` Eric Wong 2023-09-04 10:36 ` [PATCH 04/10] update devel/syscall-list to devel/sysdefs-list Eric Wong ` (6 subsequent siblings) 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta Check to ensure there's a numeric value of SIGWINCH defined for the given platform. SIGWINCH may also fire while the test is running due to a user resizing their terminal, so a boolean test to ensure it fired rather than an exact value check is more correct. --- t/sigfd.t | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/t/sigfd.t b/t/sigfd.t index 15fc818a..f6449dab 100644 --- a/t/sigfd.t +++ b/t/sigfd.t @@ -28,6 +28,7 @@ SKIP: { kill 'USR2', $$ or die "kill $!"; 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, 0); if ($sigfd) { $linux_sigfd = 1 if $^O eq 'linux'; @@ -71,7 +72,7 @@ SKIP: { 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'); - is($hit->{WINCH}->{sigfd}, 1, 'WINCH sigfd fired in event loop'); + ok($hit->{WINCH}->{sigfd}, 'WINCH sigfd fired in event loop'); } else { skip('signalfd disabled?', 10); } ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 04/10] update devel/syscall-list to devel/sysdefs-list 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong ` (2 preceding siblings ...) 2023-09-04 10:36 ` [PATCH 03/10] t/sigfd: better checks related to SIGWINCH Eric Wong @ 2023-09-04 10:36 ` Eric Wong 2023-09-04 10:36 ` [PATCH 05/10] daemon: workaround pre-EVFILT_SIGNAL signals Eric Wong ` (5 subsequent siblings) 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta We use it to dump SIGWINCH and _SC_NPROCESSORS_ONLN, so "sysdefs" is a more appropriate list for *BSD users. --- MANIFEST | 2 +- devel/{syscall-list => sysdefs-list} | 47 +++++++++++++++------------- lib/PublicInbox/Syscall.pm | 7 +++-- 3 files changed, 30 insertions(+), 26 deletions(-) rename devel/{syscall-list => sysdefs-list} (60%) diff --git a/MANIFEST b/MANIFEST index 918ec2e1..5964794e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -123,7 +123,7 @@ contrib/selinux/el7/publicinbox.fc contrib/selinux/el7/publicinbox.te devel/README devel/longest-tests -devel/syscall-list +devel/sysdefs-list examples/README examples/README.unsubscribe examples/cgit-commit-filter.lua diff --git a/devel/syscall-list b/devel/sysdefs-list similarity index 60% rename from devel/syscall-list rename to devel/sysdefs-list index 0b36c0e2..9764cc29 100755 --- a/devel/syscall-list +++ b/devel/sysdefs-list @@ -1,31 +1,37 @@ # Copyright all contributors <meta@public-inbox.org> # License: AGPL-3.0+ <http://www.gnu.org/licenses/agpl-3.0.txt> -# Dump syscall numbers under Linux and any other kernel which -# promises stable syscall numbers. This is to maintain -# PublicInbox::Syscall -# DO NOT USE this for *BSDs, none of the current BSD kernels -# we know about promise stable syscall numbers, we'll use -# Inline::C to support them. +# Dump system-specific constant numbers this is to maintain +# PublicInbox::Syscall and any other system-specific pieces. +# DO NOT USE syscall numbers for *BSDs, none of the current BSD kernels +# we know about promise stable syscall numbers (unlike Linux). +# However, sysconf(3) constants are stable ABI on all safe to dump. eval 'exec perl -S $0 ${1+"$@"}' # no shebang if 0; # running under some shell -use strict; -use v5.10.1; +use v5.12; use File::Temp 0.19; use POSIX qw(uname); +use Config; say '$machine='.(POSIX::uname())[-1]; -my $cc = $ENV{CC} // 'cc'; -my @cflags = split(/\s+/, $ENV{CFLAGS} // '-Wall'); +my $cc = $ENV{CC} // $Config{cc} // 'cc'; +my @cflags = split(/\s+/, $ENV{CFLAGS} // $Config{ccflags} // '-Wall'); my $str = do { local $/; <DATA> }; -my $tmp = File::Temp->newdir('syscall-list-XXXX', TMPDIR => 1); -my $f = "$tmp/sc.c"; -my $x = "$tmp/sc"; +$str =~ s/^\s*MAYBE\s*(\w+)\s*$/ +#ifdef $1 + D($1); +#endif +/sgxm; +my $tmp = File::Temp->newdir('sysdefs-list-XXXX', TMPDIR => 1); +my $f = "$tmp/sysdefs.c"; +my $x = "$tmp/sysdefs"; open my $fh, '>', $f or die "open $f $!"; print $fh $str or die "print $f $!"; close $fh or die "close $f $!"; -system($cc, '-o', $x, $f, @cflags) == 0 or die "cc failed \$?=$?"; +system($cc, '-o', $x, $f, @cflags) == 0 or die "$cc failed \$?=$?"; exec($x); __DATA__ -#define _GNU_SOURCE +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif #include <signal.h> #include <sys/syscall.h> #include <sys/ioctl.h> @@ -43,9 +49,7 @@ int main(void) #ifdef __linux__ D(SYS_epoll_create1); D(SYS_epoll_ctl); -#ifdef SYS_epoll_wait - D(SYS_epoll_wait); -#endif + MAYBE SYS_epoll_wait D(SYS_epoll_pwait); D(SYS_signalfd4); D(SYS_inotify_init1); @@ -59,13 +63,12 @@ int main(void) printf("FS_IOC_GETFLAGS=%#lx\nFS_IOC_SETFLAGS=%#lx\n", (unsigned long)FS_IOC_GETFLAGS, (unsigned long)FS_IOC_SETFLAGS); #endif - -#ifdef SYS_renameat2 - D(SYS_renameat2); -#endif + MAYBE SYS_renameat2 #endif /* Linux, any other OSes with stable syscalls? */ printf("size_t=%zu off_t=%zu pid_t=%zu\n", sizeof(size_t), sizeof(off_t), sizeof(pid_t)); D(SIGWINCH); + MAYBE _SC_NPROCESSORS_ONLN + return 0; } diff --git a/lib/PublicInbox/Syscall.pm b/lib/PublicInbox/Syscall.pm index 841a2106..4609b32d 100644 --- a/lib/PublicInbox/Syscall.pm +++ b/lib/PublicInbox/Syscall.pm @@ -2,7 +2,7 @@ # specifically the Debian libsys-syscall-perl 0.25-6 version to # fix upstream regressions in 0.25. # -# See devel/syscall-list in the public-inbox source tree for maintenance +# See devel/sysdefs-list in the public-inbox source tree for maintenance # <https://80x24.org/public-inbox.git>, and machines from the GCC Farm: # <https://cfarm.tetaneutral.net/> # @@ -246,7 +246,7 @@ if ($^O eq "linux") { warn <<EOM; machine=$machine ptrsize=$Config{ptrsize} has no syscall definitions git clone https://80x24.org/public-inbox.git and -Send the output of ./devel/syscall-list to meta\@public-inbox.org +Send the output of ./devel/sysdefs-list to meta\@public-inbox.org EOM } if ($u64_mod_8) { @@ -259,7 +259,8 @@ EOM } # use Inline::C for *BSD-only or general POSIX stuff. # Linux guarantees stable syscall numbering, BSDs only offer a stable libc -# use devel/syscall-list on Linux to detect new syscall numbers +# use devel/sysdefs-list on Linux to detect new syscall numbers and +# other system constants ############################################################################ # epoll functions ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 05/10] daemon: workaround pre-EVFILT_SIGNAL signals 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong ` (3 preceding siblings ...) 2023-09-04 10:36 ` [PATCH 04/10] update devel/syscall-list to devel/sysdefs-list Eric Wong @ 2023-09-04 10:36 ` Eric Wong 2023-09-04 10:36 ` [PATCH 06/10] watch: ensure children can use signal handlers Eric Wong ` (4 subsequent siblings) 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta FreeBSD and OpenBSD kqueue EVFILT_SIGNAL isn't able to handle blocked signals which were sent before the filter is created. This behavior differs from Linux signalfd, which can process blocked signals that were sent before the signalfd existed. --- lib/PublicInbox/DS.pm | 30 ++++++++++++++++++++++++++---- lib/PublicInbox/Sigfd.pm | 3 ++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm index 97546016..5168a6ee 100644 --- a/lib/PublicInbox/DS.pm +++ b/lib/PublicInbox/DS.pm @@ -24,11 +24,11 @@ use strict; 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); +use POSIX qw(WNOHANG sigprocmask SIG_SETMASK SIG_UNBLOCK); use Fcntl qw(SEEK_SET :DEFAULT O_APPEND); use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC); use Scalar::Util qw(blessed); -use PublicInbox::Syscall qw(:epoll); +use PublicInbox::Syscall qw(:epoll %SIGNUM); use PublicInbox::Tmpfile; use Errno qw(EAGAIN EINVAL ECHILD EINTR); use Carp qw(carp croak); @@ -259,19 +259,41 @@ sub PostEventLoop () { : 1 } +sub allowset ($) { + my ($sig) = @_; # { signame => whatever } + my $ret = POSIX::SigSet->new; + $ret->fillset or die "fillset: $!"; + for my $s (keys %$sig) { + my $num = $SIGNUM{$s} // POSIX->can("SIG$s")->(); + $ret->delset($num) or die "delset ($s => $num): $!"; + } + for (@UNBLOCKABLE) { $ret->delset($_) or die "delset($_): $!" } + $ret; +} + # Start processing IO events. In most daemon programs this never exits. See # C<post_loop_do> for how to exit the loop. sub event_loop (;$$) { my ($sig, $oldset) = @_; $Epoll //= _InitPoller(); require PublicInbox::Sigfd if $sig; - my $sigfd = PublicInbox::Sigfd->new($sig, 1) if $sig; + my $sigfd = $sig ? PublicInbox::Sigfd->new($sig, 1) : undef; + if ($sigfd && $sigfd->{is_kq}) { + my $tmp = allowset($sig); + local @SIG{keys %$sig} = values(%$sig); + sig_setmask($tmp, my $old = POSIX::SigSet->new); + # Unlike Linux signalfd, EVFILT_SIGNAL can't handle + # signals received before the filter is created, + # so we peek at signals here. + sig_setmask($old); + } local @SIG{keys %$sig} = values(%$sig) if $sig && !$sigfd; local $SIG{PIPE} = 'IGNORE'; if (!$sigfd && $sig) { # wake up every second to accept signals if we don't # have signalfd or IO::KQueue: - sig_setmask($oldset); + sig_setmask($oldset) if $oldset; + sigprocmask(SIG_UNBLOCK, allowset($sig)) or die "SIG_UNBLOCK: $!"; PublicInbox::DS->SetLoopTimeout(1000); } $_[0] = $sigfd = $sig = undef; # $_[0] == sig diff --git a/lib/PublicInbox/Sigfd.pm b/lib/PublicInbox/Sigfd.pm index 3c1d3811..5656baeb 100644 --- a/lib/PublicInbox/Sigfd.pm +++ b/lib/PublicInbox/Sigfd.pm @@ -31,8 +31,9 @@ sub new { $self->SUPER::new($io, EPOLLIN | EPOLLET); } else { # master main loop $self->{sock} = $io; - $self; } + $self->{is_kq} = 1 if tied(*$io); + $self; } # PublicInbox::Daemon in master main loop (blocking) ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 06/10] watch: ensure children can use signal handlers 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong ` (4 preceding siblings ...) 2023-09-04 10:36 ` [PATCH 05/10] daemon: workaround pre-EVFILT_SIGNAL signals Eric Wong @ 2023-09-04 10:36 ` Eric Wong 2023-09-04 10:36 ` [PATCH 07/10] xap_helper: support SIGTTIN+SIGTTOU worker adjustments Eric Wong ` (3 subsequent siblings) 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta Blindly using the signal set inherited from the parent process is wrong, since the parent (or grandparent) could've blocked all signals. Ensure children can process signals in the event loop when sig handlers have to use standard Perl facilities. --- lib/PublicInbox/Watch.pm | 7 +++---- script/public-inbox-watch | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/PublicInbox/Watch.pm b/lib/PublicInbox/Watch.pm index c3b5b791..a2dc125f 100644 --- a/lib/PublicInbox/Watch.pm +++ b/lib/PublicInbox/Watch.pm @@ -389,7 +389,7 @@ sub watch_atfork_child ($) { my $sig = delete $self->{sig}; $sig->{CHLD} = 'DEFAULT'; @SIG{keys %$sig} = values %$sig; - PublicInbox::DS::sig_setmask($self->{oldset}); + PublicInbox::DS::sig_setmask(PublicInbox::DS::allowset($sig)); } sub watch_atfork_parent ($) { _done_for_now($_[0]) } @@ -533,8 +533,7 @@ sub watch_nntp_init ($$) { } sub watch { # main entry point - my ($self, $sig, $oldset) = @_; - $self->{oldset} = $oldset; + my ($self, $sig) = @_; my $first_sig; $self->{sig} //= ($first_sig = $sig); my $poll = {}; # intvl_seconds => [ uri1, uri2 ] @@ -546,7 +545,7 @@ sub watch { # main entry point } watch_fs_init($self) if $self->{mdre}; local @PublicInbox::DS::post_loop_do = (sub { !$self->quit_done }); - PublicInbox::DS::event_loop($first_sig, $oldset); # calls ->event_step + PublicInbox::DS::event_loop($first_sig); # calls ->event_step _done_for_now($self); } diff --git a/script/public-inbox-watch b/script/public-inbox-watch index 2fb27343..75a9a36b 100755 --- a/script/public-inbox-watch +++ b/script/public-inbox-watch @@ -17,7 +17,7 @@ my $do_scan = 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 }; -my $oldset = PublicInbox::DS::block_signals(); +PublicInbox::DS::block_signals(); STDOUT->autoflush(1); STDERR->autoflush(1); local $0 = $0; # local since this script may be eval-ed @@ -55,5 +55,5 @@ if ($watch) { # --no-scan is only intended for testing atm, undocumented. PublicInbox::DS::requeue($scan) if $do_scan; - $watch->watch($sig, $oldset) while ($watch); + $watch->watch($sig) while ($watch); } ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 07/10] xap_helper: support SIGTTIN+SIGTTOU worker adjustments 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong ` (5 preceding siblings ...) 2023-09-04 10:36 ` [PATCH 06/10] watch: ensure children can use signal handlers Eric Wong @ 2023-09-04 10:36 ` Eric Wong 2023-09-04 10:36 ` [PATCH 08/10] xap_helper.h: include signal.h for sig* functions Eric Wong ` (2 subsequent siblings) 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta Being able to tune worker process counts on-the-fly when xap_helper gets used with -{netd,httpd,imapd} will be useful for tuning new setups. --- lib/PublicInbox/IPC.pm | 2 +- lib/PublicInbox/XapHelper.pm | 103 ++++++++++--- lib/PublicInbox/xap_helper.h | 282 ++++++++++++++++++++++++++++++----- t/xap_helper.t | 55 +++++-- 4 files changed, 371 insertions(+), 71 deletions(-) diff --git a/lib/PublicInbox/IPC.pm b/lib/PublicInbox/IPC.pm index 766c377f..528b9133 100644 --- a/lib/PublicInbox/IPC.pm +++ b/lib/PublicInbox/IPC.pm @@ -42,7 +42,7 @@ if ($enc && $dec) { # should be custom ops *ipc_thaw = \&Storable::thaw; } -my $recv_cmd = PublicInbox::Spawn->can('recv_cmd4'); +our $recv_cmd = PublicInbox::Spawn->can('recv_cmd4'); our $send_cmd = PublicInbox::Spawn->can('send_cmd4') // do { require PublicInbox::CmdIPC4; $recv_cmd //= PublicInbox::CmdIPC4->can('recv_cmd4'); diff --git a/lib/PublicInbox/XapHelper.pm b/lib/PublicInbox/XapHelper.pm index 36266e65..0e79e5e2 100644 --- a/lib/PublicInbox/XapHelper.pm +++ b/lib/PublicInbox/XapHelper.pm @@ -10,9 +10,11 @@ $GLP->configure(qw(require_order bundling no_ignore_case no_auto_abbrev)); use PublicInbox::Search qw(xap_terms); use PublicInbox::CodeSearch; use PublicInbox::IPC; +use PublicInbox::DS qw(awaitpid); +use POSIX qw(:signal_h); use Fcntl qw(LOCK_UN LOCK_EX); my $X = \%PublicInbox::Search::X; -our (%SRCH, %PIDS, $parent_pid); +our (%SRCH, %WORKERS, $parent_pid, $alive, $nworker, $workerset); our $stderr = \*STDERR; # only short options for portability in C++ implementation @@ -171,11 +173,17 @@ sub dispatch { sub recv_loop { local $SIG{__WARN__} = sub { print $stderr @_ }; my $rbuf; - while (!defined($parent_pid) || getppid != $parent_pid) { - my $req = bless {}, __PACKAGE__; - my @fds = PublicInbox::IPC::recv_cmd(\*STDIN, $rbuf, 4096*33); + my $in = \*STDIN; + while (!defined($parent_pid) || getppid == $parent_pid) { + PublicInbox::DS::sig_setmask($workerset); + my @fds = $PublicInbox::IPC::recv_cmd->($in, $rbuf, 4096*33); scalar(@fds) or exit(66); # EX_NOINPUT - $fds[0] // die "recvmsg: $!"; + if (!defined($fds[0])) { + next if $!{EINTR}; + die "recvmsg: $!"; + } + PublicInbox::DS::block_signals(); + my $req = bless {}, __PACKAGE__; my $i = 0; for my $fd (@fds) { open($req->{$i++}, '+<&=', $fd) and next; @@ -195,38 +203,89 @@ sub recv_loop { } } +sub reap_worker { # awaitpid CB + my ($pid, $nr) = @_; + delete $WORKERS{$nr}; + if (($? >> 8) == 66) { # EX_NOINPUT + $alive = undef; + PublicInbox::DS->SetLoopTimeout(1); + } elsif ($?) { + warn "worker[$nr] died \$?=$?\n"; + } + PublicInbox::DS::requeue(\&start_workers) if $alive; +} + sub start_worker ($) { my ($nr) = @_; - my $pid = fork // return warn("fork: $!"); + my $pid = fork; + if (!defined($pid)) { + warn("fork: $!"); + return undef; + }; if ($pid == 0) { - undef %PIDS; + undef %WORKERS; + PublicInbox::DS::Reset(); + $SIG{TERM} = sub { $parent_pid = -1 }; + $SIG{TTIN} = $SIG{TTOU} = 'IGNORE'; + $SIG{CHLD} = 'DEFAULT'; # Xapian may use this recv_loop(); exit(0); } else { - $PIDS{$pid} = $nr; + $WORKERS{$nr} = $pid; + awaitpid($pid, \&reap_worker, $nr); + } +} + +sub start_workers { + for my $nr (grep { !defined($WORKERS{$_}) } (0..($nworker - 1))) { + start_worker($nr) if $alive; } } +sub do_sigttou { + if ($alive && $nworker > 1) { + --$nworker; + my @nr = grep { $_ >= $nworker } keys %WORKERS; + kill('TERM', @WORKERS{@nr}); + } +} + +sub xh_alive { $alive || scalar(keys %WORKERS) } + sub start (@) { my (@argv) = @_; - local (%SRCH, %PIDS, $parent_pid); + local (%SRCH, %WORKERS); + local $alive = 1; PublicInbox::Search::load_xapian(); $GLP->getoptionsfromarray(\@argv, my $opt = { j => 1 }, 'j=i') or die 'bad args'; - return recv_loop() if !$opt->{j}; - die '-j must be >= 0' if $opt->{j} < 0; - start_worker($_) for (1..($opt->{j})); - - my $quit; - until ($quit) { - my $p = waitpid(-1, 0) or return; - if (defined(my $nr = delete $PIDS{$p})) { - $quit = 1 if ($? >> 8) == 66; # EX_NOINPUT - start_worker($nr) if !$quit; - } else { - warn "W: unknown pid=$p reaped\n"; - } + local $workerset = POSIX::SigSet->new; + $workerset->fillset or die "fillset: $!"; + for (@PublicInbox::DS::UNBLOCKABLE) { + $workerset->delset($_) or die "delset($_): $!"; } + + local $nworker = $opt->{j}; + return recv_loop() if $nworker == 0; + die '-j must be >= 0' if $nworker < 0; + for (POSIX::SIGTERM, POSIX::SIGCHLD) { + $workerset->delset($_) or die "delset($_): $!"; + } + local $parent_pid = $$; + my $sig = { + TTIN => sub { + if ($alive) { + ++$nworker; + PublicInbox::DS::requeue(\&start_workers) + } + }, + TTOU => \&do_sigttou, + CHLD => \&PublicInbox::DS::enqueue_reap, + }; + PublicInbox::DS::block_signals(); + start_workers(); + @PublicInbox::DS::post_loop_do = \&xh_alive; + PublicInbox::DS::event_loop($sig); } 1; diff --git a/lib/PublicInbox/xap_helper.h b/lib/PublicInbox/xap_helper.h index 871a381c..493a24f4 100644 --- a/lib/PublicInbox/xap_helper.h +++ b/lib/PublicInbox/xap_helper.h @@ -56,15 +56,26 @@ # define STDERR_ASSIGNABLE (0) #endif -static const int sock_fd = 0; // SOCK_SEQPACKET as stdin :P -static pid_t parent_pid; +// assert functions are used correctly (e.g. ensure hackers don't +// cause EINVAL/EFAULT). Not for stuff that can fail due to HW +// failures. +# define CHECK(type, expect, expr) do { \ + type ckvar______ = (expr); \ + assert(ckvar______ == (expect) && "BUG" && __FILE__ && __LINE__); \ +} while (0) + +static const int sock_fd = STDIN_FILENO; // SOCK_SEQPACKET as stdin :P +static volatile pid_t parent_pid; // may be set in worker sighandler (sigw) +static sigset_t fullset, workerset; +static bool alive = true; #if STDERR_ASSIGNABLE static FILE *orig_err = stderr; #endif static int orig_err_fd = -1; static void *srch_tree; // tsearch + tdelete + twalk static pid_t *worker_pids; // nr => pid -static unsigned long nworker; +static unsigned long nworker, nworker_hwm; +static int pipefds[2]; // PublicInbox::Search and PublicInbox::CodeSearch generate these: static void mail_nrp_init(void); @@ -598,11 +609,21 @@ static bool recv_req(struct req *req, char *rbuf, size_t *len) msg.msg_control = &cmsg.hdr; msg.msg_controllen = CMSG_SPACE(RECV_FD_SPACE); + // allow SIGTERM to hit + CHECK(int, 0, sigprocmask(SIG_SETMASK, &workerset, NULL)); + ssize_t r = recvmsg(sock_fd, &msg, 0); - if (r < 0) - err(EXIT_FAILURE, "recvmsg"); - if (r == 0) + if (r == 0) { exit(EX_NOINPUT); /* grandparent went away */ + } else if (r < 0) { + if (errno == EINTR) + return false; // retry recv_loop + err(EXIT_FAILURE, "recvmsg"); + } + + // success! no signals for the rest of the request/response cycle + CHECK(int, 0, sigprocmask(SIG_SETMASK, &fullset, NULL)); + *len = r; if (r > 0 && cmsg.hdr.cmsg_level == SOL_SOCKET && cmsg.hdr.cmsg_type == SCM_RIGHTS) { @@ -875,9 +896,19 @@ static void stderr_restore(FILE *tmp_err) clearerr(stderr); } +static void sigw(int sig) // SIGTERM handler for worker +{ + parent_pid = -1; // break out of recv_loop +} + static void recv_loop(void) // worker process loop { static char rbuf[4096 * 33]; // per-process + struct sigaction sa = {}; + sa.sa_handler = sigw; + + CHECK(int, 0, sigaction(SIGTERM, &sa, NULL)); + while (!parent_pid || getppid() == parent_pid) { size_t len = sizeof(rbuf); struct req req = {}; @@ -904,18 +935,6 @@ static void insert_pid(pid_t pid, unsigned nr) worker_pids[nr] = pid; } -static int delete_pid(pid_t pid) -{ - for (unsigned nr = 0; nr < nworker; nr++) { - if (worker_pids[nr] == pid) { - worker_pids[nr] = 0; - return nr; - } - } - warnx("W: unknown pid=%d reaped", (int)pid); - return -1; -} - static void start_worker(unsigned nr) { pid_t pid = fork(); @@ -925,11 +944,31 @@ static void start_worker(unsigned nr) insert_pid(pid, nr); } else { cleanup_pids(); + xclose(pipefds[0]); + xclose(pipefds[1]); + if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) + err(EXIT_FAILURE, "signal CHLD"); + if (signal(SIGTTIN, SIG_IGN) == SIG_ERR) + err(EXIT_FAILURE, "signal TTIN"); + if (signal(SIGTTOU, SIG_IGN) == SIG_ERR) + err(EXIT_FAILURE, "signal TTIN"); recv_loop(); exit(0); } } +static void start_workers(void) +{ + sigset_t old; + + CHECK(int, 0, sigprocmask(SIG_SETMASK, &fullset, &old)); + for (unsigned long nr = 0; nr < nworker; nr++) { + if (!worker_pids[nr]) + start_worker(nr); + } + CHECK(int, 0, sigprocmask(SIG_SETMASK, &old, NULL)); +} + static void cleanup_all(void) { cleanup_pids(); @@ -939,6 +978,121 @@ static void cleanup_all(void) #endif } +static void sigp(int sig) // parent signal handler +{ + static const char eagain[] = "signals coming in too fast"; + static const char bad_sig[] = "BUG: bad sig\n"; + static const char write_err[] = "BUG: sigp write: "; + char c = 0; + + switch (sig) { + case SIGCHLD: c = '.'; break; + case SIGTTOU: c = '-'; break; + case SIGTTIN: c = '+'; break; + default: + write(STDERR_FILENO, bad_sig, sizeof(bad_sig) - 1); + _exit(EXIT_FAILURE); + } + ssize_t w = write(pipefds[1], &c, 1); + if (w == sizeof(c)) return; + int e = 0; + if (w < 0) { + e = errno; + if (e == EAGAIN) { + write(STDERR_FILENO, eagain, sizeof(eagain) - 1); + return; + } + } + struct iovec iov[3]; + iov[0].iov_base = (void *)write_err; + iov[0].iov_len = sizeof(write_err) - 1; + iov[1].iov_base = (void *)(e ? strerror(e) : "zero write"); + iov[1].iov_len = strlen((const char *)iov[1].iov_base); + iov[2].iov_base = (void *)"\n"; + iov[2].iov_len = 1; + (void)writev(STDERR_FILENO, iov, MY_ARRAY_SIZE(iov)); + _exit(EXIT_FAILURE); +} + +static void reaped_worker(pid_t pid, int st) +{ + unsigned long nr = 0; + for (; nr < nworker_hwm; nr++) { + if (worker_pids[nr] == pid) { + worker_pids[nr] = 0; + break; + } + } + if (nr >= nworker_hwm) { + warnx("W: unknown pid=%d reaped $?=%d", (int)pid, st); + return; + } + if (WIFEXITED(st) && WEXITSTATUS(st) == EX_NOINPUT) + alive = false; + else if (st) + warnx("worker[%lu] died $?=%d", nr, st); + if (alive) + start_workers(); +} + +static void do_sigchld(void) +{ + while (1) { + int st; + pid_t pid = waitpid(-1, &st, WNOHANG); + if (pid > 0) { + reaped_worker(pid, st); + } else if (pid == 0) { + return; + } else { + switch (errno) { + case ECHILD: return; + case EINTR: break; // can it happen w/ WNOHANG? + default: err(EXIT_FAILURE, "BUG: waitpid"); + } + } + } +} + +static void do_sigttin(void) +{ + if (!alive) return; + void *p = reallocarray(worker_pids, nworker + 1, sizeof(pid_t)); + if (!p) { + warn("reallocarray"); + } else { + worker_pids = (pid_t *)p; + worker_pids[nworker++] = 0; + if (nworker_hwm < nworker) + nworker_hwm = nworker; + start_workers(); + } +} + +static void do_sigttou(void) +{ + if (!alive || nworker <= 1) return; + + // worker_pids array does not shrink + --nworker; + for (unsigned long nr = nworker; nr < nworker_hwm; nr++) { + pid_t pid = worker_pids[nr]; + if (pid != 0 && kill(pid, SIGTERM)) + warn("BUG?: kill(%d, SIGTERM)", (int)pid); + } +} + +static size_t living_workers(void) +{ + size_t ret = 0; + + for (unsigned long nr = 0; nr < nworker_hwm; nr++) { + if (worker_pids[nr]) + ret++; + } + return ret; +} + int main(int argc, char *argv[]) { int c; @@ -953,7 +1107,7 @@ int main(int argc, char *argv[]) err(EXIT_FAILURE, "dup(2)"); } - nworker = 0; + nworker = 1; #ifdef _SC_NPROCESSORS_ONLN long j = sysconf(_SC_NPROCESSORS_ONLN); if (j > 0) @@ -981,27 +1135,79 @@ int main(int argc, char *argv[]) errx(EXIT_FAILURE, "BUG: `-%c'", c); } } - if (nworker == 0) { + sigset_t pset; // parent-only + CHECK(int, 0, sigfillset(&pset)); + + // global sigsets: + CHECK(int, 0, sigfillset(&fullset)); + CHECK(int, 0, sigfillset(&workerset)); + +#define DELSET(sig) do { \ + CHECK(int, 0, sigdelset(&fullset, sig)); \ + CHECK(int, 0, sigdelset(&workerset, sig)); \ + CHECK(int, 0, sigdelset(&pset, sig)); \ +} while (0) + DELSET(SIGABRT); + DELSET(SIGBUS); + DELSET(SIGFPE); + DELSET(SIGILL); + DELSET(SIGSEGV); + DELSET(SIGXCPU); + DELSET(SIGXFSZ); +#undef DELSET + + if (nworker == 0) { // no SIGTERM handling w/o workers recv_loop(); - } else { - parent_pid = getpid(); - worker_pids = (pid_t *)calloc(nworker, sizeof(pid_t)); - if (!worker_pids) - err(EXIT_FAILURE, "calloc"); - for (unsigned i = 0; i < nworker; i++) - start_worker(i); - - int st; - pid_t pid; - bool quit = false; - while ((pid = wait(&st)) > 0) { - int nr = delete_pid(pid); - if (nr < 0) continue; - if (WIFEXITED(st) && WEXITSTATUS(st) == EX_NOINPUT) - quit = true; - if (!quit) - start_worker(nr); + return 0; + } + CHECK(int, 0, sigdelset(&workerset, SIGTERM)); + CHECK(int, 0, sigdelset(&workerset, SIGCHLD)); + parent_pid = getpid(); + nworker_hwm = nworker; + worker_pids = (pid_t *)calloc(nworker, sizeof(pid_t)); + if (!worker_pids) err(EXIT_FAILURE, "calloc"); + + if (pipe(pipefds)) err(EXIT_FAILURE, "pipe"); + int fl = fcntl(F_GETFL, pipefds[1]); + if (fl == -1) err(EXIT_FAILURE, "F_GETFL"); + if (fcntl(F_SETFL, pipefds[1], fl | O_NONBLOCK)) + err(EXIT_FAILURE, "F_SETFL"); + + CHECK(int, 0, sigdelset(&pset, SIGCHLD)); + CHECK(int, 0, sigdelset(&pset, SIGTTIN)); + CHECK(int, 0, sigdelset(&pset, SIGTTOU)); + + struct sigaction sa = {}; + sa.sa_handler = sigp; + + CHECK(int, 0, sigaction(SIGTTIN, &sa, NULL)); + CHECK(int, 0, sigaction(SIGTTOU, &sa, NULL)); + sa.sa_flags = SA_NOCLDSTOP; + CHECK(int, 0, sigaction(SIGCHLD, &sa, NULL)); + + CHECK(int, 0, sigprocmask(SIG_SETMASK, &pset, NULL)); + + start_workers(); + + char sbuf[64]; + while (alive || living_workers()) { + ssize_t n = read(pipefds[0], &sbuf, sizeof(sbuf)); + if (n < 0) { + if (errno == EINTR) continue; + err(EXIT_FAILURE, "read"); + } else if (n == 0) { + errx(EXIT_FAILURE, "read EOF"); + } + do_sigchld(); + for (ssize_t i = 0; i < n; i++) { + switch (sbuf[i]) { + case '.': break; // do_sigchld already called + case '-': do_sigttou(); break; + case '+': do_sigttin(); break; + default: errx(EXIT_FAILURE, "BUG: c=%c", sbuf[i]); + } } } + return 0; } diff --git a/t/xap_helper.t b/t/xap_helper.t index b68f2773..f4b3581f 100644 --- a/t/xap_helper.t +++ b/t/xap_helper.t @@ -95,21 +95,55 @@ my $test = sub { my $stats = do { local $/; <$err_rd> }; is($stats, "mset.size=6 nr_out=6\n", 'mset.size reported'); - if ($arg[-1] !~ /\('-j0'\)/) { - kill('KILL', $cinfo{pid}); + return $ar if $cinfo{pid} == $pid; + + # test worker management: + kill('TERM', $cinfo{pid}); + my $tries = 0; + do { $r = $doreq->($s, qw(test_inspect -d), $ibx_idx[0]); - %info = map { - split(/=/, $_, 2) - } split(/ /, do { local $/; <$r> }); - isnt($info{pid}, $cinfo{pid}, 'spawned new worker'); + %info = map { split(/=/, $_, 2) } + split(/ /, do { local $/; <$r> }); + } while ($info{pid} == $cinfo{pid} && ++$tries < 10); + isnt($info{pid}, $cinfo{pid}, 'spawned new worker'); + + my %pids; + $tries = 0; + my @ins = ($s, qw(test_inspect -d), $ibx_idx[0]); + kill('TTIN', $pid); + until (scalar(keys %pids) >= 2 || ++$tries > 10) { + tick; + my @r = map { $doreq->(@ins) } (0..5); + for my $fh (@r) { + my $buf = do { local $/; <$fh> } // die "read: $!"; + $buf =~ /\bpid=(\d+)/ and $pids{$1} = undef; + } + } + is(scalar keys %pids, 2, 'have two pids'); + + kill('TTOU', $pid); + %pids = (); + my $delay = $tries * 0.11 * ($ENV{VALGRIND} ? 10 : 1); + $tries = 0; + diag 'waiting '.$delay.'s for SIGTTOU'; + tick($delay); + until (scalar(keys %pids) == 1 || ++$tries > 100) { + %pids = (); + my @r = map { $doreq->(@ins) } (0..5); + for my $fh (@r) { + my $buf = do { local $/; <$fh> } // die "read: $!"; + $buf =~ /\bpid=(\d+)/ and $pids{$1} = undef; + } } + is(scalar keys %pids, 1, 'have one pid') or diag explain(\%pids); + is($info{pid}, (keys %pids)[0], 'kept oldest PID after TTOU'); + $ar; }; -my $ar; my @NO_CXX = (1); unless ($ENV{TEST_XH_CXX_ONLY}) { - $ar = $test->(qw[-MPublicInbox::XapHelper -e + my $ar = $test->(qw[-MPublicInbox::XapHelper -e PublicInbox::XapHelper::start('-j0')]); $ar = $test->(qw[-MPublicInbox::XapHelper -e PublicInbox::XapHelper::start('-j1')]); @@ -119,10 +153,10 @@ SKIP: { require PublicInbox::XapHelperCxx; PublicInbox::XapHelperCxx::check_build(); }; - skip "XapHelperCxx build: $@", 1 if $@; + skip "XapHelperCxx build: $@", 1 if $@ || $ENV{PI_NO_CXX}; @NO_CXX = $ENV{TEST_XH_CXX_ONLY} ? (0) : (0, 1); - $ar = $test->(qw[-MPublicInbox::XapHelperCxx -e + my $ar = $test->(qw[-MPublicInbox::XapHelperCxx -e PublicInbox::XapHelperCxx::start('-j0')]); $ar = $test->(qw[-MPublicInbox::XapHelperCxx -e PublicInbox::XapHelperCxx::start('-j1')]); @@ -142,6 +176,7 @@ my @id2root; close $fh; } +my $ar; for my $n (@NO_CXX) { local $ENV{PI_NO_CXX} = $n; my ($xhc, $pid) = PublicInbox::XapClient::start_helper('-j0'); ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 08/10] xap_helper.h: include signal.h for sig* functions 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong ` (6 preceding siblings ...) 2023-09-04 10:36 ` [PATCH 07/10] xap_helper: support SIGTTIN+SIGTTOU worker adjustments Eric Wong @ 2023-09-04 10:36 ` Eric Wong 2023-09-04 10:36 ` [PATCH 09/10] tests: add `+SCM_RIGHTS' as a require_mods target Eric Wong 2023-09-04 10:36 ` [PATCH 10/10] test_common: start_script: set default signals Eric Wong 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta This is documented by all sig* manpages of FreeBSD and Linux, but only OpenBSD fails to build without this header. --- lib/PublicInbox/xap_helper.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/PublicInbox/xap_helper.h b/lib/PublicInbox/xap_helper.h index 493a24f4..0f16d316 100644 --- a/lib/PublicInbox/xap_helper.h +++ b/lib/PublicInbox/xap_helper.h @@ -30,6 +30,7 @@ #include <fcntl.h> #include <limits.h> #include <search.h> +#include <signal.h> #include <stdio.h> #include <string.h> #include <sysexits.h> ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 09/10] tests: add `+SCM_RIGHTS' as a require_mods target 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong ` (7 preceding siblings ...) 2023-09-04 10:36 ` [PATCH 08/10] xap_helper.h: include signal.h for sig* functions Eric Wong @ 2023-09-04 10:36 ` Eric Wong 2023-09-04 10:36 ` [PATCH 10/10] test_common: start_script: set default signals Eric Wong 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta We'll also ensure the existing `lei' target expands to depend on `+SCM_RIGHTS', and use require_mods in t/lei-import-nntp.t and t/lei.t so they can be skipped when Inline::C and Socket::MsgHdr are missing on OpenBSD. --- lib/PublicInbox/TestCommon.pm | 15 +++++++++------ t/lei-import-nntp.t | 4 ++-- t/lei.t | 3 ++- t/xap_helper.t | 4 +--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/PublicInbox/TestCommon.pm b/lib/PublicInbox/TestCommon.pm index 6da993af..a90e89d1 100644 --- a/lib/PublicInbox/TestCommon.pm +++ b/lib/PublicInbox/TestCommon.pm @@ -134,7 +134,7 @@ sub require_mods { while (my $mod = shift(@mods)) { if ($mod eq 'lei') { require_git(2.6, $maybe ? $maybe : ()); - push @mods, qw(DBD::SQLite Search::Xapian); + push @mods, qw(DBD::SQLite Search::Xapian +SCM_RIGHTS); $mod = 'json'; # fall-through } if ($mod eq 'json') { @@ -155,6 +155,11 @@ sub require_mods { PublicInbox::Search::load_xapian()) { next; } + } elsif ($mod eq '+SCM_RIGHTS') { + if (my $msg = need_scm_rights()) { + push @need, $msg; + next; + } } elsif (index($mod, '||') >= 0) { # "Foo||Bar" my $ok; for my $m (split(/\Q||\E/, $mod)) { @@ -567,13 +572,13 @@ sub ignore_inline_c_missing { grep(!/\bInline\b/, split(/^/m, $_[0]))))); } -sub no_scm_rights () { +sub need_scm_rights () { state $ok = PublicInbox::Spawn->can('send_cmd4') || do { require PublicInbox::Syscall; - PublicInbox::Syscall->can('send_cmd4'); + PublicInbox::Syscall->can('send_cmd4'); # Linux only } || eval { require Socket::MsgHdr; 1 }; return if $ok; - 'Inline::C unconfigured/missing '. + 'need SCM_RIGHTS support: Inline::C unconfigured/missing '. '(mkdir -p ~/.cache/public-inbox/inline-c) OR Socket::MsgHdr missing'; } @@ -601,8 +606,6 @@ SKIP: { $ENV{LANG} = $ENV{LC_ALL} = 'C'; my (undef, $fn, $lineno) = caller(0); my $t = "$fn:$lineno"; - state $msg = no_scm_rights(); - skip $msg, 1 if $msg; $lei_opt = { 1 => \$lei_out, 2 => \$lei_err }; my ($daemon_pid, $for_destroy, $daemon_xrd); my $tmpdir = $test_opt->{tmpdir}; diff --git a/t/lei-import-nntp.t b/t/lei-import-nntp.t index 2c48d973..c3ee06a2 100644 --- a/t/lei-import-nntp.t +++ b/t/lei-import-nntp.t @@ -1,9 +1,9 @@ #!perl -w -# Copyright (C) 2021 all contributors <meta@public-inbox.org> +# Copyright (C) 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 PublicInbox::TestCommon; require_git 2.6; -require_mods(qw(json DBD::SQLite Search::Xapian Net::NNTP)); +require_mods(qw(lei json DBD::SQLite Search::Xapian Net::NNTP)); my ($ro_home, $cfg_path) = setup_public_inboxes; my ($tmpdir, $for_destroy) = tmpdir; my $sock = tcp_server; diff --git a/t/lei.t b/t/lei.t index 5d0fa622..d83bde69 100644 --- a/t/lei.t +++ b/t/lei.t @@ -1,7 +1,8 @@ #!perl -w -# Copyright (C) 2020-2021 all contributors <meta@public-inbox.org> +# Copyright (C) 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 PublicInbox::TestCommon; +require_mods 'lei'; use File::Path qw(rmtree); # this only tests the basic help/config/init/completion bits of lei; diff --git a/t/xap_helper.t b/t/xap_helper.t index f4b3581f..0a211329 100644 --- a/t/xap_helper.t +++ b/t/xap_helper.t @@ -3,9 +3,7 @@ # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> use v5.12; use PublicInbox::TestCommon; -require_mods(qw(DBD::SQLite Search::Xapian)); -my $msg = no_scm_rights; -plan(skip_all => $msg) if $msg; # TODO: FIFO support? +require_mods(qw(DBD::SQLite Search::Xapian +SCM_RIGHTS)); # TODO: FIFO support? use PublicInbox::Spawn qw(spawn); use Socket qw(AF_UNIX SOCK_SEQPACKET SOCK_STREAM); require PublicInbox::AutoReap; ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 10/10] test_common: start_script: set default signals 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong ` (8 preceding siblings ...) 2023-09-04 10:36 ` [PATCH 09/10] tests: add `+SCM_RIGHTS' as a require_mods target Eric Wong @ 2023-09-04 10:36 ` Eric Wong 9 siblings, 0 replies; 11+ messages in thread From: Eric Wong @ 2023-09-04 10:36 UTC (permalink / raw) To: meta We need to ensure signal handlers in the child process aren't inherited from the parent. This change was originally intended to block signals all the way until PublicInbox::Daemon and PublicInbox::Watch were fully ready to handle them (preferably via EVFILT_SIGNAL or signalfd); but that proved unrealistic. Now, all signal handlers are restored to their default values before signals are unblocked. Drop a redundant DS->Reset while we're at it. --- lib/PublicInbox/TestCommon.pm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/PublicInbox/TestCommon.pm b/lib/PublicInbox/TestCommon.pm index a90e89d1..b2774f58 100644 --- a/lib/PublicInbox/TestCommon.pm +++ b/lib/PublicInbox/TestCommon.pm @@ -478,7 +478,11 @@ sub start_script { } $tail = tail_f(@paths); } - my $pid = fork // die "fork: $!\n"; + my $oset = PublicInbox::DS::block_signals(); + require PublicInbox::OnDestroy; + my $tmp_mask = PublicInbox::OnDestroy->new( + \&PublicInbox::DS::sig_setmask, $oset); + my $pid = fork // die "fork: $!"; if ($pid == 0) { eval { PublicInbox::DS->Reset }; for (@{delete($opt->{-CLOFORK}) // []}) { @@ -504,8 +508,10 @@ sub start_script { } if ($opt->{-C}) { chdir($opt->{-C}) or die "chdir: $!" } $0 = join(' ', @$cmd); + local @SIG{keys %SIG} = map { undef } values %SIG; + local $SIG{FPE} = 'IGNORE'; # Perl default + undef $tmp_mask; if ($sub) { - eval { PublicInbox::DS->Reset }; _run_sub($sub, $key, \@argv); POSIX::_exit($? >> 8); } else { @@ -513,6 +519,7 @@ sub start_script { die "FAIL: ",join(' ', $key, @argv), ": $!\n"; } } + undef $tmp_mask; require PublicInbox::AutoReap; my $td = PublicInbox::AutoReap->new($pid); $td->{-extra} = $tail; ^ permalink raw reply related [flat|nested] 11+ messages in thread
end of thread, other threads:[~2023-09-04 10:36 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-09-04 10:35 [PATCH 00/10] signal-handling and *BSD fixes Eric Wong 2023-09-04 10:35 ` [PATCH 01/10] ds: don't block important signals we don't use Eric Wong 2023-09-04 10:35 ` [PATCH 02/10] t/sigfd: test EVFILT_SIGNAL vs signalfd differences Eric Wong 2023-09-04 10:36 ` [PATCH 03/10] t/sigfd: better checks related to SIGWINCH Eric Wong 2023-09-04 10:36 ` [PATCH 04/10] update devel/syscall-list to devel/sysdefs-list Eric Wong 2023-09-04 10:36 ` [PATCH 05/10] daemon: workaround pre-EVFILT_SIGNAL signals Eric Wong 2023-09-04 10:36 ` [PATCH 06/10] watch: ensure children can use signal handlers Eric Wong 2023-09-04 10:36 ` [PATCH 07/10] xap_helper: support SIGTTIN+SIGTTOU worker adjustments Eric Wong 2023-09-04 10:36 ` [PATCH 08/10] xap_helper.h: include signal.h for sig* functions Eric Wong 2023-09-04 10:36 ` [PATCH 09/10] tests: add `+SCM_RIGHTS' as a require_mods target Eric Wong 2023-09-04 10:36 ` [PATCH 10/10] test_common: start_script: set default signals Eric Wong
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).