Unix Programming - pipe from child to parent: the parent exits, but the child does not

This is Interesting: Free IT Magazines  
Home > Archive > Unix Programming > August 2005 > pipe from child to parent: the parent exits, but the child does not





You are viewing an archived Text-only version of the thread. To view this thread in it's original format and/or if you want to reply to this thread please [click here]

Author pipe from child to parent: the parent exits, but the child does not
Alexander Farber

2005-08-24, 7:52 am

Hi,

BACKGROUND (and the simple test case program is on the bottom):

I'm programming a load-balancer, which keeps fetching average
load values (similar to the rup command) from a list machines:

status = clnt_call(rstat_clnt, RSTATPROC_STATS,
(xdrproc_t) xdr_void, NULL,
(xdrproc_t) xdr_statstime, (char *) &stats,
timeout);
....

Because the clnt_call() blocks, I go through my list of machines,
create a pipe for each of them and fork a child, which calls that
function in a loop. Each child write()s then the 3 fetched int values
(ok, they are long on HP) via the pipe to the parent. And the parent
poll()s the reading ends of the pipes (which I also set nonblocking).

MY PROBLEM: is that when the parent exit()s, then the children don't
notice that the reading end of pipe is closed and keep running.

I understand, that as a workaround I could save the children pids
into my list of machines and - before exiting the parent -
go through that list and send a signal to each child.

But I was actually hoping, that the children would notice that event
also without a signal, since in the Stevens' Unix-Prg. book Ch. 15.2
he writes that when the SIGPIPE is ignored and the reading end of a
pipe closes, then the writing part gets -1 and errno = EPIPE.

So I have prepared a simple test case program, which forks NKIDS
number of children, who then keep writing to the parent. The parent
however sleeps 10 seconds and exits.

Now the strangs part is that for NKIDS = 1 I get the expected
behaviour - the single child gets -1 and EPIPE and exits:

bolinux72:/home/afarber> ./kids-pipe
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
written kid0, n = 5
bolinux72:/home/afarber> n = -1, errno = Broken pipe

But for NKIDS = 2 the children don't exit for some reason.

bolinux72:/home/afarber> ./kids-pipe
written kid0, n = 5
written kid1, n = 5
written kid0, n = 5
written kid1, n = 5
....keeps going....

Since I have the same behaviour on OpenBSD 3.7 stable, RedHat Linux 9
and HP-UX 11.11, I'm probably missing something very obvious... ?

Regards
Alex

PS: And here is my test program (please change NKIDS there):

bolinux72:/home/afarber> cat kids-pipe.c

#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>

extern char *__progname;

#define NKIDS 2
#define NAMELEN 5

struct kid_s {
int pipefd[2];
char name[NAMELEN];
} kids[NKIDS];

void
die(const char *s)
{
perror(s);
exit(1);
}

int
main(int argc, char *argv[])
{
int i, n;
pid_t pid;
struct kid_s *pkid;

if (signal(SIGPIPE, SIG_IGN) < 0)
die("signal error");

for (i = 0; i < NKIDS; i++) {
pkid = &kids[i];

snprintf(pkid->name, NAMELEN, "kid%d\n", i);

if (pipe(pkid->pipefd) == -1)
die("pipe error");
}

for (i = 0; i < NKIDS; i++) {
pkid = &kids[i];

switch(pid = fork()) {
case -1:
die("fork error");
/* child, keep writing to parent */
case 0:
close(pkid->pipefd[0]);
while(1) {
n = write(pkid->pipefd[1],
pkid->name, NAMELEN);
if (n != NAMELEN) {
fprintf(stderr,
"n = %d, errno = %s\n",
n, strerror(errno));
break;
}
fprintf(stderr, "written %s, n = %d\n",

pkid->name, n);
sleep(1);
}
_exit(0);
/* parent, sleep and exit */
default:
close(pkid->pipefd[1]);
}
}

sleep(10);
exit(0);
}

Alexander Farber

2005-08-24, 7:52 am

Ahhhh I've just got it myself (after 2 days struggling):
when I have more than 1 child forked, then I have
to close the reading end in all the process copies.

But how could I do it best in the test program?

And more interesting to me: why doesn't the pipe
fill up and the write() block? Who is reading it?

Regards
Alex

Alex Fraser

2005-08-24, 6:11 pm

"Alexander Farber" <Alexander.Farber@gmail.com> wrote in message
news:1124878926.938414.180530@f14g2000cwb.googlegroups.com...
> Ahhhh I've just got it myself (after 2 days struggling):
> when I have more than 1 child forked, then I have
> to close the reading end in all the process copies.
>
> But how could I do it best in the test program?


Don't create all the pipes first, call pipe(kids[i].pipefd) just before each
fork(). After fork(), in the child, close kids[n].pipefd[0] for 0<=n<=i
instead of just kids[i].pipefd[0]. In the parent, close kids[i].pipefd[1] as
you do now.

> And more interesting to me: why doesn't the pipe
> fill up and the write() block? Who is reading it?


It will fill up... eventually (try removing the sleep(1)).

With regard to the actual problem you are trying to solve: writing up to
PIPE_BUF (>= 512) bytes to a pipe is atomic; write() will write either all
or nothing (whether blocking or not), and the bytes will not be interleaved
with other writes to the same pipe. However, it isn't guaranteed that read()
will return data from just one write(), or all the data from one write().
This means that if the writes are small and (for example) you use a suitable
delimiter, you can have all children write to the same pipe.

Alex


Alexander Farber

2005-08-24, 6:11 pm

Alex Fraser wrote:
> Don't create all the pipes first, call pipe(kids[i].pipefd) just before each
> fork().


Yes, that's what I've come up with too.

> After fork(), in the child, close kids[n].pipefd[0] for 0<=n<=i
> instead of just kids[i].pipefd[0]. In the parent, close kids[i].pipefd[1] as
> you do now.


Thanks! I've missed that.

> With regard to the actual problem you are trying to solve: writing up to
> PIPE_BUF (>= 512) bytes to a pipe is atomic; write() will write either all
> or nothing (whether blocking or not), and the bytes will not be interleaved
> with other writes to the same pipe. However, it isn't guaranteed that read()
> will return data from just one write(), or all the data from one write().
> This means that if the writes are small and (for example) you use a suitable
> delimiter, you can have all children write to the same pipe.


Yes, I was thinking about it, but then I'd need to send some host id
too...

Maybe I'll do that later - if I have too many hosts to monitor by
my app (and thus too many opened pipes)

Regards
Alex

Alexander Farber

2005-08-24, 6:11 pm

Alex Fraser wrote:
> "Alexander Farber" <Alexander.Farber@gmail.com> wrote in message
> news:1124878926.938414.180530@f14g2000cwb.googlegroups.com...
>
> It will fill up... eventually (try removing the sleep(1)).


Yes, you're right here too - I removed the sleep(1) and increased
the sleep() interval in the parent and the write()s in the children
did start blocking after some time.

> With regard to the actual problem you are trying to solve: writing up to
> PIPE_BUF (>= 512) bytes to a pipe is atomic; write() will write either all
> or nothing (whether blocking or not), and the bytes will not be interleaved
> with other writes to the same pipe. However, it isn't guaranteed that read()
> will return data from just one write(), or all the data from one write().
> This means that if the writes are small and (for example) you use a suitable
> delimiter, you can have all children write to the same pipe.


I wonder actually what design is better - the one like I have now:

1) Many forked children, each with an own pipe (child -> parent),
which I put into a struct pollfd and monitor with poll().

2) Or what you suggest: one pipe for all, since the messages written
by children are small (3 ints) and putting just that 1 pipe fd into
the struct pollfd.

The problem with the second approach is that I would have to write()
a host id together with the message. Ok, just write one more int
additionally to the 3 ints above... But then I'd have to run through
my singly-linked list of hosts to locate that one host.... (Ok, I could
switch from a singly-linked list to a dynamically allocated array...)

I wonder, what would be the benefit from having just one pipe for
all...

Regards
Alex

Alex Fraser

2005-08-25, 7:49 am

"Alexander Farber" <Alexander.Farber@gmail.com> wrote in message
news:1124893284.783855.124360@z14g2000cwz.googlegroups.com...
> Alex Fraser wrote:

[snip]
>
> I wonder actually what design is better - the one like I have now:
>
> 1) Many forked children, each with an own pipe (child -> parent),
> which I put into a struct pollfd and monitor with poll().
>
> 2) Or what you suggest: one pipe for all, since the messages written
> by children are small (3 ints) and putting just that 1 pipe fd into
> the struct pollfd.
>
> The problem with the second approach is that I would have to write()
> a host id together with the message. Ok, just write one more int
> additionally to the 3 ints above... But then I'd have to run through
> my singly-linked list of hosts to locate that one host.... (Ok, I could
> switch from a singly-linked list to a dynamically allocated array...)


If you used an array, the extra int could simply be the index into the
array.

> I wonder, what would be the benefit from having just one pipe for
> all...


I guess not much in this case, but fewer descriptors is usually better in
general (eg better performance if you wait on them with poll()/etc).

FWIW, a third alternative is a small (configurable) number of child "worker"
processes. Each has a simple loop: block on the read end of a pipe until a
host is read, perform the query, and write the result to another pipe (which
may be shared between the workers). The parent keeps track of which workers
are busy/idle - initially they are all idle, writing a host makes the worker
busy, and reading a result makes a worker idle (with a shared pipe, include
the worker number in the result so you know which worker). The parent
repeatedly steps through the list of hosts keeping the workers busy,
possibly pausing at the end of the list if it took less time to run through
the list than the desired update interval.

Compared to the other two methods, the extra complexity of this - although
not massive - is probably not worth it if there are only a few hosts.
However, IMHO it is a clean and scalable design.

Alex


Alexander Farber

2005-08-25, 7:49 am

Thanks, that is a good suggestion if I ever have too many
hosts to monitor. BTW I have switched to 1 pipe now. Thanks

Sponsored Links






Free braindumps | Software forum | Database administration forum

Copyright 2003 - 2008 webservertalk.com