NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: kern/17171: Dead Child does not raise SIGCHLD until after parent reads all output on a pty.

The following reply was made to PR kern/17171; it has been noted by GNATS.

From: David Holland <>
Subject: Re: kern/17171: Dead Child does not raise SIGCHLD until after
        parent reads all output on a pty.
Date: Sat, 29 Nov 2008 21:26:45 +0000

 This behavior still exists in -current. Enclosed is an additional
 similar test program that clarifies what exactly is going on. It opens
 a pty with forkpty and optionally writes to the slave end [-w] and/or
 reads from the master end [-r].
 If neither option is given, SIGCHLD is received right away when the
 child is killed.
 With -w so the child writes to the tty, the kill kicks the child out
 of nanosleep and into some D state with no wchan listed in ps. I have
 not gone to the trouble of instrumenting the kernel to find out
 exactly where this happens, but it seems to be while closing file
 handles. It appears that the slave end of a pty waits on close for
 buffered writes to be read out of the master end.
 The child exits as soon as the buffered writes in the pty are cleared,
 either by reading from the master end with -w, or when the master end
 is closed. In either case, SIGCHLD is delivered to the parent
 The original test program only closes the master end of the pty upon
 its own exit; this leads to considerable confusion about what actually
 Conclusion: there is no problem with signal posting or delivery. The
 problem is that the child process does not actually exit when the
 submitter expects, but instead blocks closing the pty.
 I think this behavior may be somewhat undesirable but I don't think it
 is really a bug. Processes that open ptys are supposed to be able to
 read client I/O out of them, and if they do so in a timely manner
 there's no visible effect.
 The only definite bug here is that the D-state wait the child process
 falls into should have a wchan.
 Arguably it shouldn't be a D-state wait either, on the grounds that
 those are supposed to be short-term only; although interrupting close
 isn't exactly a good thing either so it isn't so clear.
  * Alternate test program for PR 17171.
  * The -w option causes the child process to write to the opened tty.
  * The -r option causes the parent process to read from the pty master end.
  * Note that each invocation of ps causes an extra SIGCHLD to be
  * received; don't be fooled by them.
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdarg.h>
 #include <limits.h>
 #include <signal.h>
 #include <err.h>
 #include <util.h>
 say(const char *fmt, ...)
        char buf[4096];
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(buf, sizeof(buf), fmt, ap);
        /* make it as atomic as possible */
        write(STDOUT_FILENO, buf, strlen(buf));
 onsigchld(int sig)
        write(STDOUT_FILENO, "*** SIGCHLD ***\n", 16);
 dops(pid_t pid, const char *what)
        char buf[128];
        say("[PARENT] ps -l of %s\n", what);
        snprintf(buf, sizeof(buf), "ps -l%d", pid);
 usage(const char *av0)
        errx(1, "usage: %s [-r] [-w]", av0);
 main(int argc, char *argv[])
        int ch;
        int parentreads = 0;
        int childwrites = 0;
        struct sigaction sa;
        pid_t pid;
        int masterfd;
        char slavename[PATH_MAX];
        ssize_t result;
        char buf[256];
        while ((ch = getopt(argc, argv, "rw")) != -1) {
                switch (ch) {
                    case 'r': parentreads = 1; break;
                    case 'w': childwrites = 1; break;
                    default: usage(argv[0]); break;
        if (optind != argc) {
        if (sigaction(SIGCHLD, NULL, &sa)) {
                err(1, "sigaction: get");
        sa.sa_handler = onsigchld;
        sa.sa_flags |= SA_NOCLDSTOP;
        if (sigaction(SIGCHLD, &sa, NULL)) {
                err(1, "sigaction: set");
        pid = forkpty(&masterfd, slavename, NULL, NULL);
        if (pid < 0) {
                err(1, "forkpty");
        if (pid == 0) {
                /* child */
                if (childwrites) {
                        say("[CHILD] la de da\n");
                /* sleep until killed */
        /* parent */
        /* 1. report who we are; wait to make sure child prints */
        say("[PARENT] my pid %d, child pid %d, tty %s\n",
            getpid(), pid, slavename);
        /* 2. inspect child; wait strictly for paranoia */
        dops(pid, "running child");
        /* 3. post SIGKILL; sleep to make sure it's processed */
        say("[PARENT] sending kill\n");
        kill(pid, SIGKILL);
        /* 4. inspect child again; wait strictly for paranoia */
        dops(pid, "killed child");
        if (parentreads) {
                /* 5. read from the pty master */
                say("[PARENT] reading from pty master\n");
                result = read(masterfd, buf, sizeof(buf));
                if (result < 0) {
                        warn("read: masterfd");
                        say("[PARENT] read failed\n");
                else {
                        say("[PARENT] read %zd bytes from pty\n", result);
                /* 6. inspect child again; wait strictly for paranoia */
                dops(pid, "killed child after ptm read");
        /* 7. now close the pty master */
        say("[PARENT] closing pty master\n");
        /* 8. inspect child again */
        dops(pid, "killed child after ptm close");
        say("[PARENT] exiting.\n");
        return 0;
 David A. Holland

Home | Main Index | Thread Index | Old Index