Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

system() returns -1 when $SIG{CHLD} in effect #11789

Closed
p5pRT opened this issue Dec 8, 2011 · 29 comments
Closed

system() returns -1 when $SIG{CHLD} in effect #11789

p5pRT opened this issue Dec 8, 2011 · 29 comments

Comments

@p5pRT
Copy link

p5pRT commented Dec 8, 2011

Migrated from rt.perl.org#105700 (status was 'resolved')

Searchable as RT105700$

@p5pRT
Copy link
Author

p5pRT commented Dec 8, 2011

From @rcaputo

Created by @rcaputo

Using $SIG{CHLD} to reap signals causes system() to return -1 on
success. The $SIG{CHLD} reaper snatches the PID from system()'s hand
before it's time to leave.

  print system('echo "without reaper​: "'), "\n";
  sub REAPER {
  while ((my $pid = waitpid(-1 , WNOHANG)) != -1) {
  print "reaped​: $pid\n";
  }
  };
  $SIG{CHLD} = \&REAPER;
  print system('echo "with reaper​: "'), "\n";

Output shows that it's wise to fear the REAPER().

  % ./perl ../../support/sigchld-system.pl
  without reaper​:
  0
  with reaper​:
  reaped​: 50696
  -1

There seems to be a workaround. I've tried to implement it, but I
don't have the core chops to get it working yet.

PP_system() uses Perl_wait4pid() to wait for a particular PID.

It could be possible to block SIGCHLD in PP_system() before
PerlProc_fork().

PERL_USES_PL_PIDSTATUS must be in effect. Perl_wait4pid() must enter
exit statuses for "uninteresting" PIDs into PL_pidstatus.
Perl_wait4pid() seems to do this after the "hard_way" goto target.

PP_system() can unblock SIGCHLD and dispatch pending PL_pidstatus
entries before returning. The presence of S_pidgone() implies that
something alreayd exists to dispatch SIGCHLD out of PL_pidstatus.

A minimal patch might get away with enabling PERL_USES_PL_PIDSTATUS on
troublesome platforms (BSD?), and masking and restoring SIGCHLD in
PP_system.

Perl Info

Flags:
    category=core
    severity=medium

Site configuration information for perl 5.15.5:

Configured by troc at Thu Dec  8 11:22:27 EST 2011.

Summary of my perl5 (revision 5 version 15 subversion 5) configuration:
  Commit id: 8b46c09ba8a3ae75055618a6e0bb4da2fac1b138
  Platform:
    osname=darwin, osvers=10.8.0, archname=darwin-thread-multi-2level
    uname='darwin macbookpoe.local 10.8.0 darwin kernel version 10.8.0: tue jun 7 16:33:36 pdt 2011; root:xnu-1504.15.3~1release_i386 i386 '
    config_args=''
    hint=recommended, useposix=true, d_sigaction=define
    useithreads=define, usemultiplicity=define
    useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
    use64bitint=define, use64bitall=define, uselongdouble=undef
    usemymalloc=n, bincompat5005=undef
  Compiler:
    cc='cc', ccflags ='-fno-common -DPERL_DARWIN -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include -I/opt/local/include',
    optimize='-O3',
    cppflags='-fno-common -DPERL_DARWIN -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include -I/opt/local/include'
    ccversion='', gccversion='4.2.1 (Apple Inc. build 5666) (dot 3)', gccosandvers=''
    intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678
    d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16
    ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
    alignbytes=8, prototype=define
  Linker and Libraries:
    ld='env MACOSX_DEPLOYMENT_TARGET=10.3 cc', ldflags =' -fstack-protector -L/usr/local/lib -L/opt/local/lib'
    libpth=/usr/local/lib /opt/local/lib /usr/lib
    libs=-ldbm -ldl -lm -lutil -lc
    perllibs=-ldl -lm -lutil -lc
    libc=, so=dylib, useshrplib=false, libperl=libperl.a
    gnulibc_version=''
  Dynamic Linking:
    dlsrc=dl_dlopen.xs, dlext=bundle, d_dlsymun=undef, ccdlflags=' '
    cccdlflags=' ', lddlflags=' -bundle -undefined dynamic_lookup -L/usr/local/lib -L/opt/local/lib -fstack-protector'

Locally applied patches:
    


@INC for perl 5.15.5:
    lib
    /Users/troc/projects/poe/poe/lib
    /Users/troc/projects/poco-client-keepalive/lib
    /Users/troc/projects/poco-client-dns/lib
    /Users/troc/projects/poco-resolver/lib
    /Users/troc/projects/poco-client-ping/lib
    /Users/troc/projects/poco-client-http/lib
    /Users/troc/projects/repo-tools/lib
    /Users/troc/projects/lex-per/lib
    /Users/troc/projects/poe/poe-test-loops/lib
    /Users/troc/projects/poe/poe-loop-event/lib
    /Users/troc/projects/poe/poe-loop-gtk/lib
    /Users/troc/projects/poe/poe-loop-tk/lib
    /Users/troc/projects/dzp-changelogfromgit/lib
    /Users/troc/projects/dzp-creditsfromgit/lib
    /Users/troc/projects/git/SVN-Dump/lib
    /Users/troc/projects/reflex/lib
    /Users/troc/projects/pod-plexus/pod-plexus/lib
    /Users/troc/projects/pod-plexus/dist-zilla-plugin-podplexus/lib
    /Users/troc/projects/pod-plexus/pod-weaver-plugin-podplexus/lib
    /Users/troc/projects/app-pipefilter/lib
    /usr/local/lib/perl5
    /Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor-aggregator-byInterface_XS_Salvador/blib/lib
    /Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor-aggregator-byInterface_XS_Salvador/blib/arch
    /Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor/blib/lib
    /Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor/blib/arch
    /Users/troc/Work/plixer/scrutinizer/trunk/lib
    /Users/troc/Work/plixer/scrutinizer/trunk
    /Users/troc/Work/plixer/externals/trunk/XS/ExUnpack/blib/lib
    /Users/troc/Work/plixer/externals/trunk/XS/ExUnpack/blib/arch
    /Users/troc/Work/plixer/keygen/trunk/lib
    /Users/troc/Work/plixer/personal/lib
    /usr/local/perl-blead/lib/site_perl/5.15.5/darwin-thread-multi-2level
    /usr/local/perl-blead/lib/site_perl/5.15.5
    /usr/local/perl-blead/lib/5.15.5/darwin-thread-multi-2level
    /usr/local/perl-blead/lib/5.15.5
    .


Environment for perl 5.15.5:
    DYLD_LIBRARY_PATH (unset)
    HOME=/Users/troc
    LANG=en_US.UTF-8
    LANGUAGE (unset)
    LD_LIBRARY_PATH (unset)
    LOGDIR (unset)
    PATH=/home/troc/bin:/usr/local/bin:/usr/local/sbin:/home/troc/projects/poe/poe-test-loops/bin:/home/troc/projects/app-pipefilter/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/home/troc/Work/plixer/personal/bin
    PERL5LIB=/Users/troc/projects/poe/poe/lib:/Users/troc/projects/poco-client-keepalive/lib:/Users/troc/projects/poco-client-dns/lib:/Users/troc/projects/poco-resolver/lib:/Users/troc/projects/poco-client-ping/lib:/Users/troc/projects/poco-client-http/lib:/Users/troc/projects/repo-tools/lib:/Users/troc/projects/lex-per/lib:/Users/troc/projects/poe/poe-test-loops/lib:/Users/troc/projects/poe/poe-loop-event/lib:/Users/troc/projects/poe/poe-loop-gtk/lib:/Users/troc/projects/poe/poe-loop-tk/lib:/Users/troc/projects/dzp-changelogfromgit/lib:/Users/troc/projects/dzp-creditsfromgit/lib:/Users/troc/projects/git/SVN-Dump/lib:/Users/troc/projects/reflex/lib:/Users/troc/projects/pod-plexus/pod-plexus/lib:/Users/troc/projects/pod-plexus/dist-zilla-plugin-podplexus/lib:/Users/troc/projects/pod-plexus/pod-weaver-plugin-podplexus/lib:/Users/troc/projects/app-pipefilter/lib:/usr/local/lib/perl5::/Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor-aggregator-byInterface_XS_Salvador/blib/lib:/Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor-aggregator-byInterface_XS_Salvador/blib/arch:/Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor/blib/lib:/Users/troc/Work/plixer/externals/trunk/XS/collector-FlowProcessor/blib/arch:/Users/troc/Work/plixer/scrutinizer/trunk/lib:/Users/troc/Work/plixer/scrutinizer/trunk:/Users/troc/Work/plixer/externals/trunk/XS/ExUnpack/blib/lib:/Users/troc/Work/plixer/externals/trunk/XS/ExUnpack/blib/arch:/Users/troc/Work/plixer/keygen/trunk/lib:/Users/troc/Work/plixer/personal/lib
    PERL_BADLANG (unset)
    SHELL=/bin/zsh


@p5pRT
Copy link
Author

p5pRT commented Dec 8, 2011

From @rcaputo

I'm attaching a Test​::More test case that illustrates the problem. It includes a PROVE_CONCEPT
constant that tests the idea of setting $SIG{CHLD}='DEFAULT' around system() calls.

@p5pRT
Copy link
Author

p5pRT commented Dec 8, 2011

From @rcaputo

sigchld-system.pl

@p5pRT
Copy link
Author

p5pRT commented Dec 8, 2011

From @rcaputo

There's precedent for blocking SIGCHLD during system().

http​://pubs.opengroup.org/onlinepubs/009695399/functions/system.html

"The system() function shall ignore the SIGINT and SIGQUIT signals, and shall block the SIGCHLD
signal, while waiting for the command to terminate. If this might cause the application to miss a
signal that would have killed it, then the application should examine the return value from
system() and take whatever action is appropriate to the application if the command terminated
due to receipt of a signal."

@p5pRT
Copy link
Author

p5pRT commented Dec 8, 2011

From @rcaputo

The blead implementation uses rsignal_save() to ignore SIGINT and SIGQUIT, but it doesn't seem
to address SIGCHLD. This seems like a departure from standard semantics.

@p5pRT
Copy link
Author

p5pRT commented Dec 8, 2011

From @rcaputo

I was pointed to two similar bugs while researching this issue. Resolving this may help to
resolve them---or make them worse.

https://rt-archive.perl.org/perl5//Public/Bug/Display.html?id=36976
(system() always returns -1 in forked child with SIG{CHLD}='IGNORE' in parent)

http​://rt.perl.org/rt3/Public/Bug/Display.html?id=18849
(fork/system bug on FreeBSD)

@p5pRT
Copy link
Author

p5pRT commented Dec 9, 2011

From @Leont

On Thu Dec 08 09​:26​:48 2011, rcaputo2 wrote​:

Using $SIG{CHLD} to reap signals causes system() to return -1 on
success. The $SIG{CHLD} reaper snatches the PID from system()'s hand
before it's time to leave.

print system('echo "without reaper​: "'), "\n";
sub REAPER {
while ((my $pid = waitpid(-1 , WNOHANG)) != -1) {
print "reaped​: $pid\n";
}
};
$SIG{CHLD} = \&REAPER;
print system('echo "with reaper​: "'), "\n";

Output shows that it's wise to fear the REAPER().

% ./perl ../../support/sigchld-system.pl
without reaper​:
0
with reaper​:
reaped​: 50696
-1

There seems to be a workaround. I've tried to implement it, but I
don't have the core chops to get it working yet.

PP_system() uses Perl_wait4pid() to wait for a particular PID.

It could be possible to block SIGCHLD in PP_system() before
PerlProc_fork().

PERL_USES_PL_PIDSTATUS must be in effect. Perl_wait4pid() must enter
exit statuses for "uninteresting" PIDs into PL_pidstatus.
Perl_wait4pid() seems to do this after the "hard_way" goto target.

PP_system() can unblock SIGCHLD and dispatch pending PL_pidstatus
entries before returning. The presence of S_pidgone() implies that
something alreayd exists to dispatch SIGCHLD out of PL_pidstatus.

A minimal patch might get away with enabling PERL_USES_PL_PIDSTATUS on
troublesome platforms (BSD?), and masking and restoring SIGCHLD in
PP_system.

Patch attached :-)

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 9, 2011

From @Leont

0001-Block-SIGCHLD-during-system-call-per-POSIX.patch
From c11ee830e255ff3173440dde1fcad0137dfc231e Mon Sep 17 00:00:00 2001
From: Leon Timmermans <fawaka@gmail.com>
Date: Fri, 9 Dec 2011 00:32:10 +0100
Subject: [PATCH] Block SIGCHLD during system() call (per POSIX)

---
 pp_sys.c |   15 +++++++++++++++
 1 files changed, 15 insertions(+), 0 deletions(-)

diff --git a/pp_sys.c b/pp_sys.c
index 78a51ae..5d40e9e 100644
--- a/pp_sys.c
+++ b/pp_sys.c
@@ -4106,9 +4106,15 @@ PP(pp_system)
 	Pid_t childpid;
 	int pp[2];
 	I32 did_pipes = 0;
+	sigset_t newset, oldset;
 
 	if (PerlProc_pipe(pp) >= 0)
 	    did_pipes = 1;
+#if (defined(HAS_SIGPROCMASK) && !defined(PERL_MICRO))
+	sigemptyset(&newset);
+	sigaddset(&newset, SIGCHLD);
+	sigprocmask(SIG_BLOCK, &newset, &oldset);
+#endif
 	while ((childpid = PerlProc_fork()) == -1) {
 	    if (errno != EAGAIN) {
 		value = -1;
@@ -4118,6 +4124,9 @@ PP(pp_system)
 		    PerlLIO_close(pp[0]);
 		    PerlLIO_close(pp[1]);
 		}
+#if (defined(HAS_SIGPROCMASK) && !defined(PERL_MICRO))
+		sigprocmask(SIG_SETMASK, &oldset, NULL);
+#endif
 		RETURN;
 	    }
 	    sleep(5);
@@ -4136,6 +4145,9 @@ PP(pp_system)
 		result = wait4pid(childpid, &status, 0);
 	    } while (result == -1 && errno == EINTR);
 #ifndef PERL_MICRO
+#ifdef HAS_SIGPROCMASK
+	    sigprocmask(SIG_SETMASK, &oldset, NULL);
+#endif
 	    (void)rsignal_restore(SIGINT, &ihand);
 	    (void)rsignal_restore(SIGQUIT, &qhand);
 #endif
@@ -4166,6 +4178,9 @@ PP(pp_system)
 	    XPUSHi(STATUS_CURRENT);
 	    RETURN;
 	}
+#if (defined(HAS_SIGPROCMASK) && !defined(PERL_MICRO))
+	sigprocmask(SIG_SETMASK, &oldset, NULL);
+#endif
 	if (did_pipes) {
 	    PerlLIO_close(pp[0]);
 #if defined(HAS_FCNTL) && defined(F_SETFD)
-- 
1.7.5.4

@p5pRT
Copy link
Author

p5pRT commented Dec 9, 2011

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Dec 9, 2011

From @Leont

On Thu Dec 08 16​:41​:00 2011, LeonT wrote​:

On Thu Dec 08 09​:26​:48 2011, rcaputo2 wrote​:

Using $SIG{CHLD} to reap signals causes system() to return -1 on
success. The $SIG{CHLD} reaper snatches the PID from system()'s hand
before it's time to leave.

print system('echo "without reaper​: "'), "\n";
sub REAPER {
while ((my $pid = waitpid(-1 , WNOHANG)) != -1) {
print "reaped​: $pid\n";
}
};
$SIG{CHLD} = \&REAPER;
print system('echo "with reaper​: "'), "\n";

Output shows that it's wise to fear the REAPER().

% ./perl ../../support/sigchld-system.pl
without reaper​:
0
with reaper​:
reaped​: 50696
-1

There seems to be a workaround. I've tried to implement it, but I
don't have the core chops to get it working yet.

PP_system() uses Perl_wait4pid() to wait for a particular PID.

It could be possible to block SIGCHLD in PP_system() before
PerlProc_fork().

PERL_USES_PL_PIDSTATUS must be in effect. Perl_wait4pid() must enter
exit statuses for "uninteresting" PIDs into PL_pidstatus.
Perl_wait4pid() seems to do this after the "hard_way" goto target.

PP_system() can unblock SIGCHLD and dispatch pending PL_pidstatus
entries before returning. The presence of S_pidgone() implies that
something alreayd exists to dispatch SIGCHLD out of PL_pidstatus.

A minimal patch might get away with enabling PERL_USES_PL_PIDSTATUS on
troublesome platforms (BSD?), and masking and restoring SIGCHLD in
PP_system.

Patch attached :-)

Leon

New patch, now avoiding declaring sigset_t's on platforms without
sigprocmask.

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 9, 2011

From @Leont

0001-Block-SIGCHLD-during-system-call-per-POSIX.patch
From 5c5e26ae6b56d73f4c2f912623d5fc04cccfdc24 Mon Sep 17 00:00:00 2001
From: Leon Timmermans <fawaka@gmail.com>
Date: Fri, 9 Dec 2011 00:32:10 +0100
Subject: [PATCH] Block SIGCHLD during system() call (per POSIX)

---
 pp_sys.c |   17 +++++++++++++++++
 1 files changed, 17 insertions(+), 0 deletions(-)

diff --git a/pp_sys.c b/pp_sys.c
index 78a51ae..1bf561d 100644
--- a/pp_sys.c
+++ b/pp_sys.c
@@ -4106,9 +4106,17 @@ PP(pp_system)
 	Pid_t childpid;
 	int pp[2];
 	I32 did_pipes = 0;
+#if (defined(HAS_SIGPROCMASK) && !defined(PERL_MICRO))
+	sigset_t newset, oldset;
+#endif
 
 	if (PerlProc_pipe(pp) >= 0)
 	    did_pipes = 1;
+#if (defined(HAS_SIGPROCMASK) && !defined(PERL_MICRO))
+	sigemptyset(&newset);
+	sigaddset(&newset, SIGCHLD);
+	sigprocmask(SIG_BLOCK, &newset, &oldset);
+#endif
 	while ((childpid = PerlProc_fork()) == -1) {
 	    if (errno != EAGAIN) {
 		value = -1;
@@ -4118,6 +4126,9 @@ PP(pp_system)
 		    PerlLIO_close(pp[0]);
 		    PerlLIO_close(pp[1]);
 		}
+#if (defined(HAS_SIGPROCMASK) && !defined(PERL_MICRO))
+		sigprocmask(SIG_SETMASK, &oldset, NULL);
+#endif
 		RETURN;
 	    }
 	    sleep(5);
@@ -4136,6 +4147,9 @@ PP(pp_system)
 		result = wait4pid(childpid, &status, 0);
 	    } while (result == -1 && errno == EINTR);
 #ifndef PERL_MICRO
+#ifdef HAS_SIGPROCMASK
+	    sigprocmask(SIG_SETMASK, &oldset, NULL);
+#endif
 	    (void)rsignal_restore(SIGINT, &ihand);
 	    (void)rsignal_restore(SIGQUIT, &qhand);
 #endif
@@ -4166,6 +4180,9 @@ PP(pp_system)
 	    XPUSHi(STATUS_CURRENT);
 	    RETURN;
 	}
+#if (defined(HAS_SIGPROCMASK) && !defined(PERL_MICRO))
+	sigprocmask(SIG_SETMASK, &oldset, NULL);
+#endif
 	if (did_pipes) {
 	    PerlLIO_close(pp[0]);
 #if defined(HAS_FCNTL) && defined(F_SETFD)
-- 
1.7.5.4

@p5pRT
Copy link
Author

p5pRT commented Dec 9, 2011

From @cpansprout

On Thu Dec 08 17​:15​:21 2011, LeonT wrote​:

On Thu Dec 08 16​:41​:00 2011, LeonT wrote​:

On Thu Dec 08 09​:26​:48 2011, rcaputo2 wrote​:

Using $SIG{CHLD} to reap signals causes system() to return -1 on
success. The $SIG{CHLD} reaper snatches the PID from system()'s hand
before it's time to leave.

print system('echo "without reaper​: "'), "\n";
sub REAPER {
while ((my $pid = waitpid(-1 , WNOHANG)) != -1) {
print "reaped​: $pid\n";
}
};
$SIG{CHLD} = \&REAPER;
print system('echo "with reaper​: "'), "\n";

Output shows that it's wise to fear the REAPER().

% ./perl ../../support/sigchld-system.pl
without reaper​:
0
with reaper​:
reaped​: 50696
-1

There seems to be a workaround. I've tried to implement it, but I
don't have the core chops to get it working yet.

PP_system() uses Perl_wait4pid() to wait for a particular PID.

It could be possible to block SIGCHLD in PP_system() before
PerlProc_fork().

PERL_USES_PL_PIDSTATUS must be in effect. Perl_wait4pid() must enter
exit statuses for "uninteresting" PIDs into PL_pidstatus.
Perl_wait4pid() seems to do this after the "hard_way" goto target.

PP_system() can unblock SIGCHLD and dispatch pending PL_pidstatus
entries before returning. The presence of S_pidgone() implies that
something alreayd exists to dispatch SIGCHLD out of PL_pidstatus.

A minimal patch might get away with enabling PERL_USES_PL_PIDSTATUS on
troublesome platforms (BSD?), and masking and restoring SIGCHLD in
PP_system.

Patch attached :-)

Leon

New patch, now avoiding declaring sigset_t's on platforms without
sigprocmask.

Leon

Since you actually understand what’s going on (unlike me), could you
adjust the OP’s test to skip on platforms where it does not apply (and
remove the bug-fix emulation)?

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Dec 11, 2011

From @Leont

On Fri, Dec 9, 2011 at 10​:38 PM, Father Chrysostomos via RT
<perlbug-followup@​perl.org> wrote​:

Since you actually understand what’s going on (unlike me)

POSIX says «The system() function shall ignore the SIGINT and SIGQUIT
signals, and shall block the SIGCHLD signal, while waiting for the
command to terminate». We are currently doing the former, but not the
latter, which can cause unspecified behavior. The patch adds blocking
for SIGCHLD, making the behavior of system in presense of a SIGCHLD
handler more sane and predictable. That appears to solve the issue
Rocco is experiencing.

could you
adjust the OP’s test to skip on platforms where it does not apply (and
remove the bug-fix emulation)?

I'm lacking a system where the test failed (e.g. FreeBSD or OS X), so
I'd rather leave that to Rocco. AFAIK it should work on any system
with SIGCHLD though.

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 11, 2011

From @rcaputo

I am attaching a new test case that I hope addresses all issues raised
on this ticket and via irc.perl.org #p5p.

@p5pRT
Copy link
Author

p5pRT commented Dec 11, 2011

From @rcaputo

#!/usr/bin/env perl

use warnings;
use strict;
use POSIX qw(WNOHANG);

use Test​::More;
use Time​::HiRes qw(sleep);

use constant TRUE => 'perl -e "exit 0"';

# Skip platforms that don't support SIGCHLD.
# It may need to C<$Config{sig_name} !~ /\bCHLD\b/> if the exists()
# test isn't reliable.

unless (exists $SIG{CHLD}) {
  plan skip_all => "$^O doesn't provide SIGCHLD";
}

plan tests => 3;

# Make sure processes forked outside system() are still reaped.

my $pid = fork();
unless ($pid) {
  note "Child PID​: $$";
  sleep 0.250;
  exit;
}

# Test system() without REAPER in place.

test_system('without reaper');

# Put REAPER() in place. REAPER() is adapted from perlipc.pod.

my @​pids;

sub REAPER {
  my $child;
  # If a second child dies while in the signal handler caused by the
  # first death, we won't get another signal. So must loop here else
  # we will leave the unreaped child as a zombie. And the next time
  # two children die we get another zombie. And so on.
  while (($child = waitpid(-1, WNOHANG)) > 0) {
  note "Reaped​: $pid";
  push @​pids, $pid;
  }

  # Reset SIGCHLD in case of SysV semantics.
  $SIG{CHLD} = \&REAPER;
}

$SIG{CHLD} = \&REAPER;

# Test system() with REAPER() in place.

test_system('with reaper');

# Give PIDs a chance.
# Wait briefly to let any pending SIGCHLDs arrive.

note "Waiting briefly for SIGCHLD...";
sleep(0.500);

# Make sure the non-system() PID was reaped.

is_deeply(
  \@​pids, [ $pid ],
  "Reaped all (and only) the processes forked."
);

# End of main tests. Begin helper functions.

sub test_system {
  my $subtest = shift();

  my $expected_zeroes = 10;
  my $got_zeroes = 0;

  # This test is looking for a race between system()'s waitpid() and a
  # signal handler. Looping a few times increases the chances of
  # catching the error.

  for (1..$expected_zeroes) {
  $got_zeroes++ unless system(TRUE);
  }

  is(
  $got_zeroes, $expected_zeroes,
  "system() $subtest succeeded $got_zeroes times out of $expected_zeroes"
  );
}

@p5pRT
Copy link
Author

p5pRT commented Dec 28, 2011

From @Leont

On Fri, Dec 9, 2011 at 11​:38 PM, Father Chrysostomos via RT
<perlbug-followup@​perl.org> wrote​:

Since you actually understand what’s going on (unlike me), could you
adjust the OP’s test to skip on platforms where it does not apply (and
remove the bug-fix emulation)?

I adapted Rocco's tests for core, see attachment

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 28, 2011

From @Leont

0002-Added-tests-for-SIGCHLD-blocking-during-system.patch
From 44f842713e2ec6de544abbff3aaf9a88f857c1bc Mon Sep 17 00:00:00 2001
From: Leon Timmermans <fawaka@gmail.com>
Date: Wed, 28 Dec 2011 23:46:54 +0200
Subject: [PATCH 2/2] Added tests for SIGCHLD blocking during system()

---
 t/op/sigdispatch.t |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/t/op/sigdispatch.t b/t/op/sigdispatch.t
index 3b8d6ec..6b0ba59 100644
--- a/t/op/sigdispatch.t
+++ b/t/op/sigdispatch.t
@@ -9,9 +9,9 @@ BEGIN {
 use strict;
 use Config;
 
-plan tests => 26;
+plan tests => 29;
 
-watchdog(15);
+watchdog(25);
 
 $SIG{ALRM} = sub {
     die "Alarm!\n";
@@ -137,3 +137,55 @@ like $@, qr/No such hook: __DIE__\\0whoops at/;
     $SIG{"KILL\0"} = sub { 1 };
     like $w, qr/No such signal: SIGKILL\\0 at/, 'Arbitrary signal lookup through %SIG is clean';
 }
+
+use constant TRUE => ('perl', '-e', 'exit 0');
+
+SKIP: {
+    skip '', 3 if is_miniperl() or not exists $SIG{CHLD};
+    require POSIX;
+    require Time::HiRes;
+
+    my $pid = fork // die "Can't fork: $!";
+    unless ($pid) {
+	note("Child PID: $$");
+	Time::HiRes::sleep(0.250);
+	exit;
+    }
+
+    test_system('without reaper');
+
+    my @pids;
+    $SIG{CHLD} = sub {
+	while (waitpid(-1, POSIX::WNOHANG()) > 0) {
+	    note "Reaped: $pid";
+	    push @pids, $pid;
+	}
+    };
+
+    test_system('with reaper');
+
+    note("Waiting briefly for SIGCHLD...");
+    Time::HiRes::sleep(0.500);
+
+    ok(@pids == 1 && $pids[0] == $pid, "Reaped all (and only) the processes forked.");
+}
+
+sub test_system {
+    my $subtest = shift;
+
+    my $expected_zeroes = 10;
+    my $got_zeroes      = 0;
+
+    # This test is looking for a race between system()'s waitpid() and a
+    # signal handler.    Looping a few times increases the chances of
+    # catching the error.
+
+    for (1..$expected_zeroes) {
+	$got_zeroes++ unless system(TRUE);
+    }
+
+    is(
+	$got_zeroes, $expected_zeroes,
+	"system() $subtest succeeded $got_zeroes times out of $expected_zeroes"
+    );
+}
-- 
1.7.5.4

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From @Leont

On Thu, Dec 29, 2011 at 12​:06 AM, Leon Timmermans <fawaka@​gmail.com> wrote​:

I adapted Rocco's tests for core, see attachment

Fixed the patch to use the new perl instead of the system perl to
execute, as the latter may not be available.

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From @Leont

0002-Added-tests-for-SIGCHLD-blocking-during-system.patch
From 44f842713e2ec6de544abbff3aaf9a88f857c1bc Mon Sep 17 00:00:00 2001
From: Leon Timmermans <fawaka@gmail.com>
Date: Wed, 28 Dec 2011 23:46:54 +0200
Subject: [PATCH 2/2] Added tests for SIGCHLD blocking during system()

---
 t/op/sigdispatch.t |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/t/op/sigdispatch.t b/t/op/sigdispatch.t
index 3b8d6ec..6b0ba59 100644
--- a/t/op/sigdispatch.t
+++ b/t/op/sigdispatch.t
@@ -9,9 +9,9 @@ BEGIN {
 use strict;
 use Config;
 
-plan tests => 26;
+plan tests => 29;
 
-watchdog(15);
+watchdog(25);
 
 $SIG{ALRM} = sub {
     die "Alarm!\n";
@@ -137,3 +137,55 @@ like $@, qr/No such hook: __DIE__\\0whoops at/;
     $SIG{"KILL\0"} = sub { 1 };
     like $w, qr/No such signal: SIGKILL\\0 at/, 'Arbitrary signal lookup through %SIG is clean';
 }
+
+use constant TRUE => ('perl', '-e', 'exit 0');
+
+SKIP: {
+    skip '', 3 if is_miniperl() or not exists $SIG{CHLD};
+    require POSIX;
+    require Time::HiRes;
+
+    my $pid = fork // die "Can't fork: $!";
+    unless ($pid) {
+	note("Child PID: $$");
+	Time::HiRes::sleep(0.250);
+	exit;
+    }
+
+    test_system('without reaper');
+
+    my @pids;
+    $SIG{CHLD} = sub {
+	while (waitpid(-1, POSIX::WNOHANG()) > 0) {
+	    note "Reaped: $pid";
+	    push @pids, $pid;
+	}
+    };
+
+    test_system('with reaper');
+
+    note("Waiting briefly for SIGCHLD...");
+    Time::HiRes::sleep(0.500);
+
+    ok(@pids == 1 && $pids[0] == $pid, "Reaped all (and only) the processes forked.");
+}
+
+sub test_system {
+    my $subtest = shift;
+
+    my $expected_zeroes = 10;
+    my $got_zeroes      = 0;
+
+    # This test is looking for a race between system()'s waitpid() and a
+    # signal handler.    Looping a few times increases the chances of
+    # catching the error.
+
+    for (1..$expected_zeroes) {
+	$got_zeroes++ unless system(TRUE);
+    }
+
+    is(
+	$got_zeroes, $expected_zeroes,
+	"system() $subtest succeeded $got_zeroes times out of $expected_zeroes"
+    );
+}
-- 
1.7.5.4

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From @Leont

On Thu, Dec 29, 2011 at 12​:41 PM, Leon Timmermans <fawaka@​gmail.com> wrote​:

Fixed the patch to use the new perl instead of the system perl to
execute, as the latter may not be available.

And now for real…

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From @Leont

0002-Added-tests-for-SIGCHLD-blocking-during-system.patch
From 3856004c9dd9bc49a14ab65b7526fc97e1208828 Mon Sep 17 00:00:00 2001
From: Leon Timmermans <fawaka@gmail.com>
Date: Wed, 28 Dec 2011 23:46:54 +0200
Subject: [PATCH 2/2] Added tests for SIGCHLD blocking during system()

---
 t/op/sigdispatch.t |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/t/op/sigdispatch.t b/t/op/sigdispatch.t
index 3b8d6ec..e4e6207 100644
--- a/t/op/sigdispatch.t
+++ b/t/op/sigdispatch.t
@@ -9,9 +9,9 @@ BEGIN {
 use strict;
 use Config;
 
-plan tests => 26;
+plan tests => 29;
 
-watchdog(15);
+watchdog(25);
 
 $SIG{ALRM} = sub {
     die "Alarm!\n";
@@ -137,3 +137,55 @@ like $@, qr/No such hook: __DIE__\\0whoops at/;
     $SIG{"KILL\0"} = sub { 1 };
     like $w, qr/No such signal: SIGKILL\\0 at/, 'Arbitrary signal lookup through %SIG is clean';
 }
+
+use constant TRUE => ($^X, '-e', 'exit 0');
+
+SKIP: {
+    skip '', 3 if is_miniperl() or not exists $SIG{CHLD};
+    require POSIX;
+    require Time::HiRes;
+
+    my $pid = fork // die "Can't fork: $!";
+    unless ($pid) {
+	note("Child PID: $$");
+	Time::HiRes::sleep(0.250);
+	exit;
+    }
+
+    test_system('without reaper');
+
+    my @pids;
+    $SIG{CHLD} = sub {
+	while (waitpid(-1, POSIX::WNOHANG()) > 0) {
+	    note "Reaped: $pid";
+	    push @pids, $pid;
+	}
+    };
+
+    test_system('with reaper');
+
+    note("Waiting briefly for SIGCHLD...");
+    Time::HiRes::sleep(0.500);
+
+    ok(@pids == 1 && $pids[0] == $pid, "Reaped all (and only) the processes forked.");
+}
+
+sub test_system {
+    my $subtest = shift;
+
+    my $expected_zeroes = 10;
+    my $got_zeroes      = 0;
+
+    # This test is looking for a race between system()'s waitpid() and a
+    # signal handler.    Looping a few times increases the chances of
+    # catching the error.
+
+    for (1..$expected_zeroes) {
+	$got_zeroes++ unless system(TRUE);
+    }
+
+    is(
+	$got_zeroes, $expected_zeroes,
+	"system() $subtest succeeded $got_zeroes times out of $expected_zeroes"
+    );
+}
-- 
1.7.5.4

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From @cpansprout

On Thu Dec 29 02​:42​:48 2011, LeonT wrote​:

On Thu, Dec 29, 2011 at 12​:06 AM, Leon Timmermans <fawaka@​gmail.com>
wrote​:

I adapted Rocco's tests for core, see attachment

Fixed the patch to use the new perl instead of the system perl to
execute, as the latter may not be available.

I think you resubmitted the same patch.

Anyway, I’m getting test failures after applying this and your patch to
fix the bug​:

...
ok 25
ok 26 - Arbitrary signal lookup through %SIG is clean
# Child PID​: 84134
ok 27 - system() without reaper succeeded 10 times out of 10
# Looks like you planned 29 tests but ran 26.
# Reaped​: 84134
# Reaped​: 84134
ok 28 - system() with reaper succeeded 10 times out of 10
# Waiting briefly for SIGCHLD...
not ok 29 - Reaped all (and only) the processes forked.
# Failed at op/sigdispatch.t line 170

Before applying the fix, I got two ‘not oks’​:

ok 25
ok 26 - Arbitrary signal lookup through %SIG is clean
# Child PID​: 92657
ok 27 - system() without reaper succeeded 10 times out of 10
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Looks like you planned 29 tests but ran 26.
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
not ok 28 - system() with reaper succeeded 0 times out of 10
# Failed at op/sigdispatch.t line 188
# got "0"
# expected "10"
# Waiting briefly for SIGCHLD...
not ok 29 - Reaped all (and only) the processes forked.
# Failed at op/sigdispatch.t line 170

I’m not even going to pretend I understand any of this.

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From @cpansprout

On Thu Dec 29 06​:15​:52 2011, sprout wrote​:

On Thu Dec 29 02​:42​:48 2011, LeonT wrote​:

On Thu, Dec 29, 2011 at 12​:06 AM, Leon Timmermans <fawaka@​gmail.com>
wrote​:

I adapted Rocco's tests for core, see attachment

Fixed the patch to use the new perl instead of the system perl to
execute, as the latter may not be available.

I think you resubmitted the same patch.

Anyway, I’m getting test failures after applying this and your patch to
fix the bug​:

...
ok 25
ok 26 - Arbitrary signal lookup through %SIG is clean
# Child PID​: 84134
ok 27 - system() without reaper succeeded 10 times out of 10
# Looks like you planned 29 tests but ran 26.
# Reaped​: 84134
# Reaped​: 84134
ok 28 - system() with reaper succeeded 10 times out of 10
# Waiting briefly for SIGCHLD...
not ok 29 - Reaped all (and only) the processes forked.
# Failed at op/sigdispatch.t line 170

Before applying the fix, I got two ‘not oks’​:

ok 25
ok 26 - Arbitrary signal lookup through %SIG is clean
# Child PID​: 92657
ok 27 - system() without reaper succeeded 10 times out of 10
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Looks like you planned 29 tests but ran 26.
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
# Reaped​: 92657
not ok 28 - system() with reaper succeeded 0 times out of 10
# Failed at op/sigdispatch.t line 188
# got "0"
# expected "10"
# Waiting briefly for SIGCHLD...
not ok 29 - Reaped all (and only) the processes forked.
# Failed at op/sigdispatch.t line 170

I’m not even going to pretend I understand any of this.

And now when I run it the tests pass. The weather hasn’t changed the
past few hours (40s, cloudy), so it must be the time of day, or sunspots.

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From tchrist@perl.com

Father Chrysostomos wrote​:

And now when I run it the tests pass. The weather hasn’t changed the
past few hours (40s, cloudy), so it must be the time of day, or sunspots.

Funny you should mention that.

  http​://news.slashdot.org/story/11/12/29/1425232/sun-storms-may-affect-radios-cell-phones-today

--tom

@p5pRT
Copy link
Author

p5pRT commented Dec 29, 2011

From @Leont

On Thu, Dec 29, 2011 at 4​:15 PM, Father Chrysostomos via RT
<perlbug-followup@​perl.org> wrote​:

I think you resubmitted the same patch.

I did, see my email a few hours later that did contain a different patch

Anyway, I’m getting test failures after applying this and your patch to
fix the bug​:

My patch is fucked. I wasn't paying enough attention, it's not testing
right. If it did I would have noticed the watchdog is interfering with
the test. I think I'll have to put this in a file of its own to work
properly. I'll write a new patch tomorrow.

I’m not even going to pretend I understand any of this.

Then I'll pretend I do ;-)

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 30, 2011

From @Leont

On Thu, Dec 29, 2011 at 11​:34 PM, Leon Timmermans <fawaka@​gmail.com> wrote​:

My patch is fucked. I wasn't paying enough attention, it's not testing
right. If it did I would have noticed the watchdog is interfering with
the test. I think I'll have to put this in a file of its own to work
properly. I'll write a new patch tomorrow.

I think this one is better.

Leon

@p5pRT
Copy link
Author

p5pRT commented Dec 30, 2011

From @Leont

0002-Added-tests-for-SIGCHLD-blocking-during-system.patch
From 6082975c0b6fd141f919ee2276a6afbdb951c2a4 Mon Sep 17 00:00:00 2001
From: Leon Timmermans <fawaka@gmail.com>
Date: Fri, 30 Dec 2011 20:02:07 +0200
Subject: [PATCH 2/2] Added tests for SIGCHLD blocking during system()

---
 MANIFEST         |    1 +
 t/op/sigsystem.t |   63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+), 0 deletions(-)
 create mode 100644 t/op/sigsystem.t

diff --git a/MANIFEST b/MANIFEST
index 53acd0c..d4a9c63 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -5265,6 +5265,7 @@ t/op/runlevel.t			See if die() works from perl_call_*()
 t/op/select.t			See if 0- and 1-argument select works
 t/op/setpgrpstack.t		See if setpgrp works
 t/op/sigdispatch.t		See if signals are always dispatched
+t/op/sigsystem.t		See if system and SIGCHLD handlers play together nicely
 t/op/sleep.t			See if sleep works
 t/op/smartkve.t			See if smart deref for keys/values/each works
 t/op/smartmatch.t		See if the ~~ operator works
diff --git a/t/op/sigsystem.t b/t/op/sigsystem.t
new file mode 100644
index 0000000..0197ad9
--- /dev/null
+++ b/t/op/sigsystem.t
@@ -0,0 +1,63 @@
+#!perl -w
+
+BEGIN {
+      require './test.pl';
+}
+
+use strict;
+use constant TRUE => ($^X, '-e', 'exit 0');
+use Data::Dumper;
+
+plan tests => 4;
+
+SKIP: {
+    skip 'Platform doesn\'t support SIGCHLD', 3 if is_miniperl() or not exists $SIG{CHLD};
+    require POSIX;
+    require Time::HiRes;
+
+    my $pid = fork // die "Can't fork: $!";
+    unless ($pid) {
+	note("Child PID: $$");
+	Time::HiRes::sleep(0.250);
+	POSIX::_exit(0);
+    }
+
+    test_system('without reaper');
+
+    my @pids;
+    $SIG{CHLD} = sub {
+	while ((my $child = waitpid(-1, POSIX::WNOHANG())) > 0) {
+	    note "Reaped: $child";
+	    push @pids, $child;
+	}
+    };
+
+    test_system('with reaper');
+
+    note("Waiting briefly for SIGCHLD...");
+    Time::HiRes::sleep(0.500);
+
+    ok(@pids == 1, 'Reaped only one process');
+    ok($pids[0] == $pid, "Reaped the right process.") or diag(Dumper(\@pids));
+}
+
+sub test_system {
+    my $subtest = shift;
+
+    my $expected_zeroes = 10;
+    my $got_zeroes      = 0;
+
+    # This test is looking for a race between system()'s waitpid() and a
+    # signal handler.    Looping a few times increases the chances of
+    # catching the error.
+
+    for (1..$expected_zeroes) {
+	$got_zeroes++ unless system(TRUE);
+    }
+
+    is(
+	$got_zeroes, $expected_zeroes,
+	"system() $subtest succeeded $got_zeroes times out of $expected_zeroes"
+    );
+}
+
-- 
1.7.5.4

@p5pRT
Copy link
Author

p5pRT commented Dec 31, 2011

From @cpansprout

On Fri Dec 30 10​:22​:50 2011, LeonT wrote​:

On Thu, Dec 29, 2011 at 11​:34 PM, Leon Timmermans <fawaka@​gmail.com>
wrote​:

My patch is .... I wasn't paying enough attention, it's not testing
right. If it did I would have noticed the watchdog is interfering with
the test. I think I'll have to put this in a file of its own to work
properly. I'll write a new patch tomorrow.

I think this one is better.

Thank you. Applied as c56bc16 and the fix as b1cf9e9.

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Dec 31, 2011

@cpansprout - Status changed from 'open' to 'resolved'

@p5pRT p5pRT closed this as completed Dec 31, 2011
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant