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 <dholland-bugs%netbsd.org@localhost>
To: gnats-bugs%netbsd.org@localhost
Cc:
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
immediately.
The original test program only closes the master end of the pty upon
its own exit; this leads to considerable confusion about what actually
happens.
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>
static
void
say(const char *fmt, ...)
{
char buf[4096];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
/* make it as atomic as possible */
write(STDOUT_FILENO, buf, strlen(buf));
}
static
void
onsigchld(int sig)
{
(void)sig;
write(STDOUT_FILENO, "*** SIGCHLD ***\n", 16);
}
static
void
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);
system(buf);
}
static
void
usage(const char *av0)
{
errx(1, "usage: %s [-r] [-w]", av0);
}
int
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) {
usage(argv[0]);
}
if (sigaction(SIGCHLD, NULL, &sa)) {
err(1, "sigaction: get");
}
sa.sa_handler = onsigchld;
sa.sa_flags |= SA_NOCLDSTOP;
sigemptyset(&sa.sa_mask);
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 */
sleep(1000);
_exit(101);
}
/* 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);
sleep(1);
/* 2. inspect child; wait strictly for paranoia */
dops(pid, "running child");
sleep(1);
/* 3. post SIGKILL; sleep to make sure it's processed */
say("[PARENT] sending kill\n");
kill(pid, SIGKILL);
sleep(1);
/* 4. inspect child again; wait strictly for paranoia */
dops(pid, "killed child");
sleep(1);
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);
}
sleep(1);
/* 6. inspect child again; wait strictly for paranoia */
dops(pid, "killed child after ptm read");
sleep(1);
}
/* 7. now close the pty master */
say("[PARENT] closing pty master\n");
close(masterfd);
sleep(1);
/* 8. inspect child again */
dops(pid, "killed child after ptm close");
sleep(1);
say("[PARENT] exiting.\n");
return 0;
}
--
David A. Holland
dholland%netbsd.org@localhost
Home |
Main Index |
Thread Index |
Old Index