Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 8 Dec 2002 18:18:47 +0100
From:      Anton Berezin <tobez@FreeBSD.org>
To:        alan <alan@pair.com>
Cc:        arch@FreeBSD.org
Subject:   SA_NOCLDWAIT and waitXXX strangeties  (Was: Re: ports/45972: Perl system() calls will hang if the process has other forked children.)
Message-ID:  <20021208171847.GE35282@heechee.tobez.org>
In-Reply-To: <200212041830.gB4IU2Mi007843@freefall.freebsd.org>
References:  <200212041830.gB4IU2Mi007843@freefall.freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
Alan,

On Wed, Dec 04, 2002 at 10:30:02AM -0800, alan wrote:

>  The problem is, when you set SA_NOCLDWAIT, subsequent calls to wait()
>  (or wait4()) wait for All child processes to exit, not just the
>  process ID that wait() is called on.  Since Perl's system() calls
>  wait4() on its recently forked child, the system() call doesn't return
>  until All of the perl process's children exit.  This doesn't seem like
>  particularly desirable behavior, but it is documented in FreeBSD's
>  sigaction man page:
>  
>             SA_NOCLDWAIT    If this bit is set when calling sigaction() for the
>                             SIGCHLD signal, the system will not create zombie
>                             processes when children of the calling process
>                             exit.  If the calling process subsequently issues a
>                             wait(2) (or equivalent), it blocks until all of the
>                             calling process's child processes terminate, and
>                             then returns a value of -1 with errno set to
>                             ECHILD.

I don't like this behavior.  Personally, I would like to think that it
should not wait for all children if any of the waitXXX() explicitly
specifies a pid to wait for.  Obviously, there is a difference between
how I would like to interpret the docs and the reality.  :-/  So in this
particular instance it looks to me like a bug (or at least, a gray area)
in FreeBSD, which should be discussed, hence the copy to arch-.

(The complete description of the problem, for the benefits of arch-
folks, can be found here:
http://www.freebsd.org/cgi/query-pr.cgi?pr=45972)

>  The quick fix is to stop using SA_NOCLDWAIT when it ignores SIGCHLD.
>  This may create unwanted zombie processes, though.

Why would it?  IIRC, the BSD semantics for SIG_IGN for SIGCHLD is
exactly `do not create zombies' semantics, so the combination of a perl
program setting SIG_IGN for SIGCHLD and perl itself setting SA_NOCLDWAIT
at the same time looks like an attempt to do the same thing twice.  But,
regardless of whether this behavior of perl is a bug or not, I would
still like to hear our signal handling experts on the issue.

>  The better fix is probably not to wait() at all if SIGCHLD is
>  currently being ignored with SA_NOCLDWAIT.

One has to waitXXX() in order to implement a system(3)-like call.
FreeBSD's own system(3) does wait4(), just like perl's system() does.

>  Below is C code which compiles and runs on FreeBSD using gcc, and
>  which demonstrates the difference in behavior when SA_NOCLDWAIT is
>  used and is not used.  I would like confirmation that this Is in fact
>  the expected behavior of FreeBSD, and that in this case Perl is making
>  incorrect assumptions about how FreeBSD will behave.

Exactly.  Once you have identified the problem (thanks!), it is easy to
fix perl.  The question is that maybe FreeBSD also need fixing.

>  Thanks,
>  
>  Alan Ferrency
>  pair Networks
>  alan@pair.com
>  
>  
>  
>  #include <signal.h>
>  #include <unistd.h>
>  #include <stdio.h>
>  #include <stdlib.h>
>  #include <errno.h>
>  
>  int main (void) {
>    int chld, stat;
>    struct sigaction act;
>  
>    act.sa_handler = SIG_IGN;
>    sigemptyset(&act.sa_mask);
>    act.sa_flags = 0;
>  
>    sigaction(SIGCHLD, &act, 0);
>    printf("fork 1: no one will wait for me.\n");
>    if (!fork()) {
>      sleep(120);
>      printf("  Fork 1 exiting\n");
>      exit(0);
>    }
>  
>    printf("fork 2\n");
>    if (chld = fork()) {
>      printf("  Parent: waiting for fork 2 to exit\n");
>      wait4(chld, &stat, 0, 0) == -1 && errno == EINTR;
>    } else { /* child process */
>      sleep(1);
>      printf("  fork 2 exiting\n");
>      exit(0);
>    }
>    printf("  Parent done with fork 2.\n");
>  
>  
>    act.sa_handler = SIG_IGN;
>    sigemptyset(&act.sa_mask);
>    act.sa_flags = SA_NOCLDWAIT;
>    sigaction(SIGCHLD, &act, 0);
>  
>    printf("fork 3\n");
>    if (chld = fork()) {
>      printf("  Parent: waiting for fork 3 to exit\n");
>      wait4(chld, &stat, 0, 0) == -1 && errno == EINTR;
>    } else { /* child process */
>      sleep(1);
>      printf("  Fork 3 exiting\n");
>      exit(0);
>    }
>    printf("  Parent done with fork 3.\n");
>  
>    printf("done\n");
>  
>    return 0;
>  }

Cheers,
=Anton.
-- 
| Anton Berezin                |      FreeBSD: The power to serve |
| catpipe Systems ApS   _ _ |_ |           http://www.FreeBSD.org |
| tobez@catpipe.net    (_(_||  |                tobez@FreeBSD.org | 
| +45 7021 0050                |         Private: tobez@tobez.org |

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-arch" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20021208171847.GE35282>