From nobody@FreeBSD.org Fri Jan 26 22:56:39 2007 Return-Path: Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id 9898516A4C4 for ; Fri, 26 Jan 2007 22:56:39 +0000 (UTC) (envelope-from nobody@FreeBSD.org) Received: from www.freebsd.org (www.freebsd.org [69.147.83.33]) by mx1.freebsd.org (Postfix) with ESMTP id B957313C484 for ; Fri, 26 Jan 2007 22:56:38 +0000 (UTC) (envelope-from nobody@FreeBSD.org) Received: from www.freebsd.org (localhost [127.0.0.1]) by www.freebsd.org (8.13.1/8.13.1) with ESMTP id l0QMuW1A048704 for ; Fri, 26 Jan 2007 22:56:32 GMT (envelope-from nobody@www.freebsd.org) Received: (from nobody@localhost) by www.freebsd.org (8.13.1/8.13.1/Submit) id l0QMuWGK048701; Fri, 26 Jan 2007 22:56:32 GMT (envelope-from nobody) Message-Id: <200701262256.l0QMuWGK048701@www.freebsd.org> Date: Fri, 26 Jan 2007 22:56:32 GMT From: Alan Ferrency To: freebsd-gnats-submit@FreeBSD.org Subject: wait4() erroneously waits for all children when SIGCHLD is SIG_IGN X-Send-Pr-Version: www-3.0 >Number: 108390 >Category: standards >Synopsis: [kernel] [patch] wait4() erroneously waits for all children when SIGCHLD is SIG_IGN [regression] >Confidential: no >Severity: serious >Priority: medium >Responsible: kib >State: closed >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Fri Jan 26 23:00:35 GMT 2007 >Closed-Date: Mon Jul 06 11:13:41 UTC 2009 >Last-Modified: Mon Jul 06 11:13:41 UTC 2009 >Originator: Alan Ferrency >Release: 6.2-RELEASE, 6.1-STABLE, 5.5-PRERELEASE >Organization: pair Networks, Inc. >Environment: FreeBSD .pair.com 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Mon Jan 15 22:21:03 EST 2007 mlehner@mayon.pair.com:/usr/obj/usr/src/sys/6PAIRc i386 FreeBSD .pair.com 6.1-STABLE FreeBSD 6.1-STABLE #3: Tue May 16 12:04:45 EDT 2006 cap@pit54.pair.com:/usr/src/sys/i386/compile/PAIR_WS i386 FreeBSD .pair.net 5.5-PRERELEASE FreeBSD 5.5-PRERELEASE #0: Mon Mar 6 14:49:09 EST 2006 root@gw06.pair.net:/usr/obj/usr/src/sys/FIVEGWb i386 >Description: If sigaction() is used to set a SIG_IGN handler for SIGCHLD, and then wait4() is used to wait for a specific child process to exit, wait4() erroneously waits for all child processes to exit before it returns. This is the incorrect behavior when the SA_NOCLDWAIT flag is not specified in sigaction(). However, this is what happens on the systems described above, even when SA_NOCLDWAIT is not specified. The correct behavior in this case is for wait4() to return as soon as the specified child process exits. When SIGCHLD is not set to SIG_IGN, wait4() behaves this way. The sample script, below, works correctly in all cases on FreeBSD 4.8-RELEASE, but fails on the versions of FreeBSD specified above. Thanks, Alan Ferrency >How-To-Repeat: Included here is a script, as well as sample output from the script when it fails. The script demonstrates the correct behavior without SIG_IGN, and the failing behavior with it. Sample output: default SIGCHLD 1169851899 55091 P spawned running child 1169851899 55091 P spawned short running child; waitpid 1169851899 55092 C1 Long child sleeping 1169851899 55093 C2 short child: exiting immediately 1169851899 55091 P short child finished 1169851899 55091 P waiting for long running child 1169851909 55092 C1 Long exiting 1169851909 55091 P long child finished. End of test. IGNORE SIGCHLD 1169851909 55091 P spawned running child 1169851909 55091 P spawned short running child; waitpid 1169851909 55097 C1 Long child sleeping 1169851909 55098 C2 short child: exiting immediately 1169851919 55097 C1 Long exiting 1169851919 55091 P short child finished 1169851919 55091 P waiting for long running child 1169851919 55091 P long child finished. End of test. The first column of output is epoch timestamp. In the first case, the first wait4() call on the short-lived child process returns immediately, and then there is a 10 second delay when the second wait4() call waits for the long-running process to complete. In the second case, the first wait4() call on the short-lived child PID waits 10 seconds for the long-lived child to finish, before it returns. Then, there is no delay at the second wait4() call. This is incorrect. The failure script was compiled with gcc, with no additional libraries or compile time options: #include #include #include #include #include #define SLEEP 10 void test_it (char *); int main (void) { struct sigaction act; /* With the default SIGCHLD, everything works as expected. */ test_it("default SIGCHLD"); /* When we set up sig CHLD to ignore, it causes our wait4 to wait for all PIDs not just one of them */ act.sa_handler = SIG_IGN; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGCHLD, &act, 0); test_it("IGNORE SIGCHLD"); exit(0); } void Log (char *msg) { printf("%10d %5d %s\n", time(NULL), getpid(), msg); } void test_it (char *msg) { int long_pid, short_pid, stat; printf ("\n\n%s\n\n", msg); /* Fork a long running child */ if (long_pid = fork()) { Log("P spawned running child"); /* Fork a short running child */ if (short_pid = fork()) { Log("P spawned short running child; waitpid"); wait4(short_pid, &stat, 0, 0); Log("P short child finished"); } else { /* the short running child */ Log("C2 short child: exiting immediately"); exit(0); } Log("P waiting for long running child"); wait4(long_pid, &stat, 0, 0); Log("P long child finished. End of test."); } else { /* the long running child*/ Log("C1 Long child sleeping"); sleep(SLEEP); Log("C1 Long exiting"); exit(0); } } >Fix: Unknown >Release-Note: >Audit-Trail: From: Alan Ferrency To: bug-followup@FreeBSD.org, alan@pair.com Cc: Subject: Re: kern/108390: wait4() erroneously waits for all children when SIGCHLD is SIG_IGN Date: Mon, 29 Jan 2007 17:03:18 -0500 (EST) This was tested on FreeBSD 5.2 and was found to fail there as well: FreeBSD .org 5.2-CURRENT FreeBSD 5.2-CURRENT #1: Thu Jul 8 22:21:34 EDT 2004 From: Alan Ferrency To: bug-followup@FreeBSD.org Cc: Alan Ferrency Subject: Re: kern/108390: wait4() erroneously waits for all children when SIGCHLD is SIG_IGN Date: Thu, 1 Feb 2007 11:38:48 -0500 (EST) Perusing the source of sys/kern/kern_exit.c on the FreeBSD CVSWEB, I believe I found the problematic change. The change is in exit1() and not in wait4() or related functions. The change is between r1.156 and r1.157, as shown here: http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/kern/kern_exit.c.diff?r1=1.156&r2=1.157&f=h Here, exit1() was modified so that if a process has SIGCHLD set to SIG_IGN, and it calls exit(), then this is treated the same as if SA_NOCLDWAIT was set. That is: the parent process is not notified that the child exited; instead, pid 1 is notified. I modified my test script to output the return value and errno of the failing wait4() call, and it verifies that when waiting too long for the short-running process, wait4() is returning with ENOCHLD (no child processes) and not because it received notification of a child process exiting. The current implementation makes it invalid for any process to execute a blocking wait() if it is ignoring SIGCHLD. This does not seem correct. Just because I don't want to receive a signal when my children exit, this does not mean I won't want to ask them whether they're alive or not. Alan Ferrency From: Alan Ferrency To: bug-followup@FreeBSD.org Cc: Subject: Re: kern/108390: wait4() erroneously waits for all children when SIGCHLD is SIG_IGN Date: Thu, 1 Feb 2007 11:42:17 -0500 (EST) I apologize: This should read "ECHILD", errno 10. > I modified my test script to output the return value and errno of the > failing wait4() call, and it verifies that when waiting too long for the > short-running process, wait4() is returning with ENOCHLD (no child > processes) and not because it received notification of a child process > exiting. Alan From: Alan Ferrency To: bug-followup@FreeBSD.org Cc: Subject: Re: kern/108390: wait4() erroneously waits for all children when SIGCHLD is SIG_IGN Date: Thu, 1 Feb 2007 12:48:38 -0500 (EST) > Perusing the source of sys/kern/kern_exit.c on the FreeBSD CVSWEB, I > believe I found the problematic change. > > http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/kern/kern_exit.c.diff?r1=1.156&r2=1.157&f=h I removed this change, and recompiled the 6.2 kernel, and the test script provided here behaved as we expected it to (as it does on FreeBSD 4.8 and earlier). Thanks, Alan Ferrency From: Jilles Tjoelker To: bug-followup@FreeBSD.org, alan@pair.com Cc: Subject: Re: bin/108390: [libc] [patch] wait4() erroneously waits for all children when SIGCHLD is SIG_IGN [regression] Date: Sun, 5 Apr 2009 18:08:46 +0200 POSIX seems to agree with what FreeBSD does. The change you refer to just makes ignoring SIGCHLD do the same as SA_NOCLDWAIT, i.e. avoid creating zombies from terminated child processes. Fact of the matter is that signal(SIGCHLD, SIG_IGN) and SA_NOCLDWAIT are pretty useless, even if this were to be "fixed". If the child process terminates while you are not executing waitpid(), the status is just lost and it is even possible for a new child process to get the same pid. Also think of functions like system(3), wordexp(3) and grantpt(3) (the latter only on freebsd 5, 6 and 7), which create child processes to do some of their work. If you want to check if the pid still exists (beware of pid reuse), use kill(pid, 0); to wait (not portably and with the same caveat), use kqueue/kevent with EVFILT_PROC. Additionally, if your code does not have to be portable, you can use rfork(2) with the RFNOWAIT flag to avoid zombie creation for specific child processes only. If that's not possible, consider forking twice and waiting for the first child immediately or doing execl("/bin/sh", "sh", "...&", (const char *)NULL); in a child process. -- Jilles Tjoelker From: Alan Ferrency To: Jilles Tjoelker Cc: bug-followup@FreeBSD.org Subject: Re: bin/108390: [libc] [patch] wait4() erroneously waits for all children when SIGCHLD is SIG_IGN [regression] Date: Mon, 6 Apr 2009 10:24:37 -0400 (EDT) Hi, I realized after submitting this bug fix, that the original patch I removed was intended to be a feature and not a bug. We don't like it, but we've worked around it by using wait() in our SIGCHLD handlers instead of SIG_IGN. Our main problem was in Perl, where there is no longer a straightfoward way to avoid zombies and ignore child process exit codes on FreeBSD. Perl and other similar languages provide portable interfaces to things like fork(), wait(), and system(), but don't provide interfaces to native things like kqueue() and rfork(). In this case, we set $SIG{CHLD} to IGNORE, forked, and then called system(). Perl's system() internally uses wait4pid() to synchronously wait for the system() child processes to end, and to collect their status. But on FreeBSD, this hangs until all child processes are finished, not just the system() call. The Perl people insisted this wasn't their problem, they're just mimicking what the native OS does; but now that I see your explanation, I'm not sure I agree with them anymore. One way or another, there's a functionality mismatch here between FreeBSD and Perl which makes $SIG{CHLD} = IGNORE basically useless. Our basic goal was to ignore the status of most child processes, while still retrieving the exit code from a system() call. It seems that SIG_IGN is not the solution for this, in FreeBSD. Overall, this change puts a huge damper on cross-platform portability. But, just as with SA_NOCLDWAIT, it seems that FreeBSD is supporting features that Linux isn't (but should be), so I guess I just need to wait for them to catch up. Thanks for the followup, I'm sorry to submit a report for not-a-bug. Alan Ferrency pair Networks, Inc. On Sun, 5 Apr 2009, Jilles Tjoelker wrote: > POSIX seems to agree with what FreeBSD does. The change you refer to > just makes ignoring SIGCHLD do the same as SA_NOCLDWAIT, i.e. avoid > creating zombies from terminated child processes. > > Fact of the matter is that signal(SIGCHLD, SIG_IGN) and SA_NOCLDWAIT are > pretty useless, even if this were to be "fixed". If the child process > terminates while you are not executing waitpid(), the status is just > lost and it is even possible for a new child process to get the same > pid. Also think of functions like system(3), wordexp(3) and grantpt(3) > (the latter only on freebsd 5, 6 and 7), which create child processes to > do some of their work. > > If you want to check if the pid still exists (beware of pid reuse), use > kill(pid, 0); to wait (not portably and with the same caveat), use > kqueue/kevent with EVFILT_PROC. > > Additionally, if your code does not have to be portable, you can use > rfork(2) with the RFNOWAIT flag to avoid zombie creation for specific > child processes only. If that's not possible, consider forking twice and > waiting for the first child immediately or doing execl("/bin/sh", "sh", > "...&", (const char *)NULL); in a child process. > > -- > Jilles Tjoelker > State-Changed-From-To: open->closed State-Changed-By: brucec State-Changed-When: Thu Apr 9 21:35:25 UTC 2009 State-Changed-Why: Not a bug in FreeBSD. http://www.freebsd.org/cgi/query-pr.cgi?pr=108390 From: Jilles Tjoelker To: bug-followup@FreeBSD.org Cc: Subject: Re: bin/108390: [libc] [patch] wait4() erroneously waits for all children when SIGCHLD is SIG_IGN [regression] Date: Sat, 11 Apr 2009 00:23:56 +0200 Maybe this can be reopened as a change-request. The way wait4() has worked when SA_NOCLDWAIT is set has been a bit strange all since it was introduced in 1997 in http://svn.freebsd.org/viewvc/base?view=revision&revision=29340 The comment there seems to agree with what POSIX says, which is not as useful as it could reasonably be: /* * If this was the last child of our parent, notify * parent, so in case he was wait(2)ing, he will * continue. */ if (LIST_EMPTY(&pp->p_children)) wakeup(pp); However, if you pass wait4() a pid or pgid that matches no child processes, it returns ECHILD immediately, without waiting for any other children to terminate (see kern_wait() in kern_exit.c). Together with signal semantics this leads to strange results. A wait4() on a specific process will normally wait for all child processes to terminate, but if a signal is caught in the meantime, wait4() is restarted and returns ECHILD immediately if all processes matching the argument have already terminated. I have tried this by catching SIGALRM with an empty handler and calling alarm(2) before test_it("IGNORE SIGCHLD"); "P short child finished" happens after 2 seconds, "P waiting for long running child" happens after 8 more seconds. Another way this can lead to strange results is if something else wakes up the proc pointer. It seems this can happen if you use SA_NOCLDWAIT in a multithreaded process, and have one thread wait for a child process and another do a vfork(). The child process from the vfork will wake up the proc pointer to notify that it has execed, and this will wake up both the wait and the vfork. (By the way, vfork() blocking only the calling thread and not the entire process is rather weird considering the original reason for the blocking.) Possible related issue: what if the vfork child did not exec and the wakeup is suppressed; does this freeze the parent until all other children are gone? Perhaps there are other things that can wakeup the proc pointer? I think removing the if (LIST_EMPTY(&pp->p_children)) condition and always doing the wakeup(pp) will yield a more consistent behaviour, which seems more useful for applications. wait4() with SA_NOCLDWAIT will then wait for all matching child processes to terminate and return ECHILD (unless there are still zombies left from a time when SA_NOCLDWAIT was not set). The behaviour described in POSIX is available by specifying any child process (-1). gavin reports that the test program works as the submitter wants (wait4(short_pid, ...) returns immediately after short_pid terminates) on Solaris 10. While doing this, I also noticed a bug in kern_wait(). Ptrace reparents a process to a debugger. When the process exits, the debugger will pick it up in kern_wait() which reparents the process back to its original parent and signals the original parent as for a normal exit. This code does not check for SA_NOCLDWAIT or SIGCHLD being set SIG_IGN, and will leave a zombie anyway. Note that the special handling for SIG_IGN for SIGCHLD in FreeBSD 5 and newer works pretty much the same way as SA_NOCLDWAIT, so it is not much of a factor in the above discussion. -- Jilles Tjoelker State-Changed-From-To: closed->open State-Changed-By: gavin State-Changed-When: Thu Apr 16 12:37:19 UTC 2009 State-Changed-Why: Reopen, there's some question about whether the bug reported here is indeed a bug: FreeBSD behaves differently to both Solaris 10 and Linux (Ubuntu 8.04) in how wait4() functions. Responsible-Changed-From-To: freebsd-bugs->freebsd-standards Responsible-Changed-By: gavin Responsible-Changed-When: Thu Apr 16 12:37:19 UTC 2009 Responsible-Changed-Why: Over to -standards. There is some debate as to what the correct behaviour is here, hopefully -standards will be able to provide an opinion. http://www.freebsd.org/cgi/query-pr.cgi?pr=108390 From: Gavin Atkinson To: bug-followup@FreeBSD.org Cc: Subject: Re: standards/108390: [libc] [patch] wait4() erroneously waits for all children when SIGCHLD is SIG_IGN [regression] Date: Thu, 16 Apr 2009 13:45:44 +0100 Results from supplied test script on Solaris 10: default SIGCHLD 1239885904 2692 P spawned running child 1239885904 2693 C1 Long child sleeping 1239885904 2692 P spawned short running child; waitpid 1239885904 2694 C2 short child: exiting immediately 1239885904 2692 P short child finished 1239885904 2692 P waiting for long running child 1239885914 2693 C1 Long exiting 1239885914 2692 P long child finished. End of test. IGNORE SIGCHLD 1239885914 2692 P spawned running child 1239885914 2695 C1 Long child sleeping 1239885914 2692 P spawned short running child; waitpid 1239885914 2696 C2 short child: exiting immediately 1239885914 2692 P short child finished 1239885914 2692 P waiting for long running child 1239885924 2695 C1 Long exiting 1239885924 2692 P long child finished. End of test. Results from Linux (Ubuntu 8.10): default SIGCHLD 1239885858 2057 P spawned running child 1239885858 2058 C1 Long child sleeping 1239885858 2059 C2 short child: exiting immediately 1239885858 2057 P spawned short running child; waitpid 1239885858 2057 P short child finished 1239885858 2057 P waiting for long running child 1239885868 2058 C1 Long exiting 1239885868 2057 P long child finished. End of test. IGNORE SIGCHLD 1239885868 2057 P spawned running child 1239885868 2060 C1 Long child sleeping 1239885868 2061 C2 short child: exiting immediately 1239885868 2057 P spawned short running child; waitpid 1239885868 2057 P short child finished 1239885868 2057 P waiting for long running child 1239885878 2060 C1 Long exiting 1239885878 2057 P long child finished. End of test. Responsible-Changed-From-To: freebsd-standards->kib Responsible-Changed-By: kib Responsible-Changed-When: Sun Apr 19 19:54:31 UTC 2009 Responsible-Changed-Why: Take. http://www.freebsd.org/cgi/query-pr.cgi?pr=108390 State-Changed-From-To: open->patched State-Changed-By: linimon State-Changed-When: Mon Apr 20 19:52:29 UTC 2009 State-Changed-Why: From misfiled PR standards/133873: Date: Mon, 20 Apr 2009 14:35:08 +0000 (UTC) From: dfilter@FreeBSD.ORG (dfilter service) To: bug-followup@FreeBSD.org Subject: Re: standard/108390: commit references a PR Author: kib Date: Mon Apr 20 14:34:55 2009 New Revision: 191313 URL: http://svn.freebsd.org/changeset/base/191313 Log: On the exit of the child process which parent either set SA_NOCLDWAIT or ignored SIGCHLD, unconditionally wake up the parent instead of doing this only when the child is a last child. This brings us in line with other U**xes that support SA_NOCLDWAIT. If the parent called waitpid(childpid), then exit of the child should wake up the parent immediately instead of forcing it to wait for all children to exit. Reported by: Alan Ferrency Submitted by: Jilles Tjoelker PR: 108390 MFC after: 2 weeks Modified: head/sys/kern/kern_exit.c Modified: head/sys/kern/kern_exit.c ============================================================================== --- head/sys/kern/kern_exit.c Mon Apr 20 13:53:40 2009 (r191312) +++ head/sys/kern/kern_exit.c Mon Apr 20 14:34:55 2009 (r191313) @@ -504,13 +504,13 @@ exit1(struct thread *td, int rv) proc_reparent(p, initproc); p->p_sigparent = SIGCHLD; PROC_LOCK(p->p_pptr); + /* - * If this was the last child of our parent, notify - * parent, so in case he was wait(2)ing, he will + * Notify parent, so in case he was wait(2)ing or + * executiing waitpid(2) with our pid, he will * continue. */ - if (LIST_EMPTY(&pp->p_children)) - wakeup(pp); + wakeup(pp); } else mtx_unlock(&p->p_pptr->p_sigacts->ps_mtx); _______________________________________________ svn-src-all@freebsd.org mailing list http://lists.freebsd.org/mailman/listinfo/svn-src-all To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org" http://www.freebsd.org/cgi/query-pr.cgi?pr=108390 State-Changed-From-To: patched->closed State-Changed-By: kib State-Changed-When: Mon Jul 6 11:12:10 UTC 2009 State-Changed-Why: Merged to 7 in r191812. http://www.freebsd.org/cgi/query-pr.cgi?pr=108390 >Unformatted: