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

Signals not cleared by fork #11061

Closed
p5pRT opened this issue Jan 21, 2011 · 11 comments
Closed

Signals not cleared by fork #11061

p5pRT opened this issue Jan 21, 2011 · 11 comments

Comments

@p5pRT
Copy link

p5pRT commented Jan 21, 2011

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

Searchable as RT82580$

@p5pRT
Copy link
Author

p5pRT commented Jan 21, 2011

From philipgwyn@gmail.com

Created by philipgwyn@gmail.com

Perl_csighandler() (the sigaction() signal handler) sets a flag in
PL_psig_pend[]. These flags are checked in Perl_despatch_signals()
via PERL_ASYNC_CHECK in Perl_runops_standard(), if I undertand the code
correctly.

This introduces a race condition for pp_fork()​:
  - PERL_ASYNC_CHECK
  All flags clear
  - A signal arrives
  A flag is set
  - pp_fork() is called
  - fork() is called
  The flag is set in both the child and the parent.

The quick fix​:
  - pp_fork() masks all signals
  - fork() is called
  - in the parent​:
  - unmask signals
  - return PID
  - in the child​:
  - clear signal flags
  - unmask signals

There is a problem with this though​: Unless I mis-understand how signal
masking works, signals that arrive between mask() call land the unmask()
call (in the parent) will be lost.

The hard fix (the shared-nothing model)​:
  - Create a one-way pipe
  - Signal handlers write one byte (the signal #) to the pipe
  - PERL_ASYNC_CHECK does a non-blocking read, gets the byte(s)
  - The signal is dispatched via Perl_sighandler as normal

Included is a short script that exercises the problem. This script has
provoked the problem with perl versions 5.8.8, 5.10.1, 5.12.1, 5.12.2 (and
probably more) on Linux (CentOS, Gentoo, Ubuntu) and Mac OS X.

#!/usr/bin/perl

use strict;
use warnings;
use Carp;

use POSIX "​:sys_wait_h";

my $parent = $$;
my $todo = 16; # Maximum number of child processes in play.
my $running = 1;

print "my pid=$$ parent pid=$parent - this is the master control process, the only forker\n";

$SIG{CHLD} = sub {
  confess "my pid=$$ parent pid=$parent - child process mistakenly got SIGCHLD somehow\n" if $parent != $$;

  # NB - waitpid() < 0 is legal on MSWin32.
  while (waitpid(-1, WNOHANG) > 0) {
  $todo++;
  $running = 0 if $?;
  }
};

sub forker {
  confess "my pid=$$ expected=$parent - child process somehow got into the main loop\n" if $$ != $parent;

  while ($todo > 0) {
  my $child_pid = fork();
  confess "my pid=$$ parent pid=$parent - fork failed​: $!" unless
  defined $child_pid;

  # Child here. Trigger SIGCHLD in the parent.
  exit unless $child_pid;

  # Parent. One fewer child to fork.
  $todo--;
  }
}

# Known that this spins.
# We want to stress fork() and $SIG{CHLD}.
forker() while $running;

Perl Info

Flags:
    category=core
    severity=medium

Site configuration information for perl 5.12.2:

Configured by fil at Fri Jan 21 13:53:48 EST 2011.

Summary of my perl5 (revision 5 version 12 subversion 2) configuration:
   
  Platform:
    osname=linux, osvers=2.6.18-194.17.1.el5, archname=x86_64-linux
    uname='linux george-dev5.localdomain 2.6.18-194.17.1.el5 #1 smp wed sep 29 12:50:31 edt 2010 x86_64 x86_64 x86_64 gnulinux '
    config_args='-de -Dprefix=/home/fil/perl5/perlbrew/perls/perl-5.12.2'
    hint=recommended, useposix=true, d_sigaction=define
    useithreads=undef, usemultiplicity=undef
    useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
    use64bitint=define, use64bitall=define, uselongdouble=undef
    usemymalloc=n, bincompat5005=undef
  Compiler:
    cc='cc', ccflags ='-fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64',
    optimize='-O2',
    cppflags='-fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include'
    ccversion='', gccversion='4.1.2 20080704 (Red Hat 4.1.2-48)', 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='cc', ldflags =' -fstack-protector -L/usr/local/lib'
    libpth=/usr/local/lib /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib64
    libs=-lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc
    perllibs=-lnsl -ldl -lm -lcrypt -lutil -lc
    libc=/lib/libc-2.5.so, so=so, useshrplib=false, libperl=libperl.a
    gnulibc_version='2.5'
  Dynamic Linking:
    dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E'
    cccdlflags='-fPIC', lddlflags='-shared -O2 -L/usr/local/lib -fstack-protector'

Locally applied patches:
    


@INC for perl 5.12.2:
    /home/fil/perl5lib/lib64/perl5/site_perl/5.8.8
    /home/fil/perl5lib/lib/perl5/site_perl/5.8.8
    /home/fil/perl5/perlbrew/perls/perl-5.12.2/lib/site_perl/5.12.2/x86_64-linux
    /home/fil/perl5/perlbrew/perls/perl-5.12.2/lib/site_perl/5.12.2
    /home/fil/perl5/perlbrew/perls/perl-5.12.2/lib/5.12.2/x86_64-linux
    /home/fil/perl5/perlbrew/perls/perl-5.12.2/lib/5.12.2
    .


Environment for perl 5.12.2:
    HOME=/home/fil
    LANG (unset)
    LANGUAGE (unset)
    LD_LIBRARY_PATH (unset)
    LOGDIR (unset)
    PATH=/home/fil/perl5/perlbrew/bin:/home/fil/perl5/perlbrew/perls/perl-5.12.2/bin:/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/fil/bin
    PERL5LIB=/home/fil/perl5lib/lib64/perl5/site_perl/5.8.8:/home/fil/perl5lib/lib/perl5/site_perl/5.8.8
    PERLBREW_PATH=/home/fil/perl5/perlbrew/bin:/home/fil/perl5/perlbrew/perls/perl-5.12.2/bin
    PERLBREW_PERL=perl-5.12.2
    PERLBREW_ROOT=/home/fil/perl5/perlbrew
    PERLBREW_VERSION=0.15
    PERL_BADLANG (unset)
    SHELL=/bin/bash

@p5pRT
Copy link
Author

p5pRT commented Jan 22, 2011

From philipgwyn@gmail.com

Here is a test case (from Rocco Caputo) that triggers the problem using
USR1​:

#!/usr/bin/perl

use strict;
use warnings;
use Carp;

use POSIX "​:sys_wait_h";

my $parent = $$;
my $todo = 16; # Maximum number of child processes in play.
my $running = 1;

print "my pid=$$ parent pid=$parent - this is the master control
process, the only forker\n";

$SIG{USR1} = sub {
  return if $$ == $parent;
  confess "my pid=$$ parent pid=$parent - child process got SIGUSR1
destined for parent\n" if $parent != $$;
};

$SIG{CHLD} = sub {

  # NB - waitpid() < 0 is legal on MSWin32.
  $todo++ while waitpid(-1, WNOHANG) > 0;
};

sub forker {
  confess "my pid=$$ expected=$parent - child process somehow got into
the main loop\n" if $$ != $parent;

  while ($todo > 0) {
  my $child_pid = fork();
  confess "my pid=$$ parent pid=$parent - fork failed​: $!" unless
defined $child_pid;

  # Child here. Trigger SIGCHLD in the parent.
  unless ($child_pid) {
  kill USR1 => $parent;
  exit;
  }

  # Parent. One fewer child to fork.
  $todo--;
  }
}

# Known that this spins.
# We want to stress fork() and $SIG{CHLD}.
forker() while $running;

@p5pRT
Copy link
Author

p5pRT commented Jan 22, 2011

philipgwyn@gmail.com - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Jan 23, 2011

From blgl@stacken.kth.se

2011-01-21 kl. 22.37 skrev Philip Gwyn​:

There is a problem with this though​: Unless I mis-understand how signal
masking works, signals that arrive between mask() call land the unmask()
call (in the parent) will be lost.

You misunderstand. It's ignored signals that are lost, not blocked ones.

/Bo Lindbergh

@p5pRT
Copy link
Author

p5pRT commented Jan 25, 2011

From philipgwyn@gmail.com

On Sun Jan 23 04​:44​:57 2011, blgl@​stacken.kth.se wrote​:

You misunderstand. It's ignored signals that are lost, not blocked ones.

A short test shows that while CHLD makes it through, waitpid() will
always return -1. Tested on 5.8.8, 5.10.1, 5.12.2.

Note that a return value of "-1" could mean that child processes are
being automatically reaped, as described in perlipc.

@p5pRT
Copy link
Author

p5pRT commented Jan 25, 2011

From philipgwyn@gmail.com

fork-6

@p5pRT
Copy link
Author

p5pRT commented Jan 26, 2011

From philipgwyn@gmail.com

http​://search.cpan.org/dist/IPC-SafeFork/

@p5pRT
Copy link
Author

p5pRT commented Apr 17, 2012

From @Leont

On Tue Jan 25 12​:06​:00 2011, Leolo wrote​:

On Sun Jan 23 04​:44​:57 2011, blgl@​stacken.kth.se wrote​:

You misunderstand. It's ignored signals that are lost, not blocked
ones.

A short test shows that while CHLD makes it through, waitpid() will
always return -1. Tested on 5.8.8, 5.10.1, 5.12.2.

Perl reaps processes when closing filehandles created through «open
"-|"», there's nothing weird or worrisome about your observation.

Leon

@p5pRT
Copy link
Author

p5pRT commented May 22, 2012

From @Leont

On Tue Apr 17 04​:09​:16 2012, LeonT wrote​:

On Tue Jan 25 12​:06​:00 2011, Leolo wrote​:

On Sun Jan 23 04​:44​:57 2011, blgl@​stacken.kth.se wrote​:

You misunderstand. It's ignored signals that are lost, not blocked
ones.

A short test shows that while CHLD makes it through, waitpid() will
always return -1. Tested on 5.8.8, 5.10.1, 5.12.2.

Perl reaps processes when closing filehandles created through �open
"-|"�, there's nothing weird or worrisome about your observation.

Leon

There's a patch that fixes this in the smoke-me/leont/signals-fork
branch. Given it's a non-deterministic issue, I don't have a test though.

Leon

@p5pRT
Copy link
Author

p5pRT commented May 25, 2012

From @cpansprout

Fixed in eb3d0a5.

@p5pRT
Copy link
Author

p5pRT commented May 25, 2012

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

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