0
votes

I am creating a TCP service that forks a new process each time a client connects. Before the fork I set up a pipe so the child can send statistics gathered during the connection back to the parent. The parent closes the writing end and the child closes the reading end, and the parent maintains an array of reading-end file descriptors, one per child.

I am not sure what to do with these file descriptors when the child finishes with the connection and exits. Does the child need to notify the parent via the pipe that it is about to exit so the parent can close the pipe? Or can the parent detect the broken pipe automatically after the child exits and close it?

The code in the parent program is running a loop with select() detecting activity on the listening socket and on the read ends of the children's pipes. Each child may send multiple messages to the parent as it runs.

In general, what should the parent process do with a pipe file descriptor when a child exits?

4

4 Answers

3
votes

In your case, the parent process shall close writing end of the pipe right after fork. Then it can read its statistic data until EOF (end-of-file) and then close the reading end of the pipe.

4
votes

First pass: before it was clear that there was a loop using select() and that children sent multiple messages.

If the parent process maintains an array of file descriptors, it needs to also associate each file descriptor with a child process. If the children send a single small statistics message before they die, then when the main program waits for dead children, it knows which child died, so it can then close the file descriptor for the child that it just spotted dieing (after making sure the pipe is empty by doing one or more final reads).

An alternative mechanism uses select() or poll() or a related function that reports when a read operation on a file descriptor would not hang. When it detects EOF (zero bytes read) from a pipe, it knows the child died. However, this is probably fiddlier to deal with.

It isn't entirely clear from your question whether there's a single message from the child process as it exits, or whether there is a 'stream of consciousness' statistics reports as the child is working. If there's a single message (that's smaller than the pipe buffer size), then life is easy. If there's a stream of messages or the message is longer than the pipe buffer size, you have to think more carefully about coordination — you can't detect messages only when the child dies.

Second pass: after the extra information became available.

If you're already using select() in a loop, then when a child dies, you will get a 'pipe ready for reading' indication from select() and you will get 0 bytes from read() which indicates EOF on that pipe. You should then close that pipe (and wait for one or more children with waitpid(), probably using W_NOHANG — there should be at least one corpse to be collected — so you don't have zombies kicking around for protracted times).

A strict answer to your last question is: when the only child with the write end of a pipe dies, the parent should close the read end of that pipe to release resources for later reuse.

0
votes

broken pipe happens when you write to the pipe but no fd is open for reading from that pipe. So it doesn't apply to your case. In your case, since your parent is reading from the pipe, it should read EOF when child exits (if you have closed the write end in your parent process correctly, otherwise it will just block since it assumes there will still be things to read in the future). Then you can safely close the read fd in your parent process.

In general

If parent writes and child reads, you do need to worry about broken pipe which is when the child closes the read fd, and parent gets SIGPIPE as it keeps writing to the pipe. SIGPIPE by default terminates the process, so you may want to set up a signal handler to make it do whatever you want (if you don't want it to just terminate).

0
votes

Let's see the see the cases differently for Parent having 1 child and children.

  • Parent having 1 child. When child process exits and parent is waiting on read end, parent will also exit. Here's the code
//fd[0]     //read end
//fd[1]     //write end

#include <unistd.h>
#include <stdio.h>
#include <errno.h>              //For errno
#include <stdlib.h>              //exit()

void DumpAndExit(char* str){
    perror (str);
    printf ("errno: %d", errono);
    exit(0);  
}

int main(){
  int fd[2], pid = -1;

  if (pipe(fd) < 0)
    DumpAndExit ("pipe");

  if (pid = fork() < 0) {
    DumpAndExit ("fork");
  }
  else if (pid == 0) {                              //Child
    close(fd[0]);                                   //Close read end
    printf("In Child \n");
    sleep(2);
    exit(0);
  } else {                                         //Parent
    close(fd[1]);                                  //close write
    waitpid(-1);                                   //Parent will wait for child
    printf("Parent waiting\n");
    char buf[4] = {};
    read(fd[0], buf, sizeof(buf));                //reads from pipe
    printf ("Read from child: %s", buf);
  }
}

# ./a.out
In child 
Parent waiting
Read from child:
#

In very simple words:

  • Every process have a PCB(struct task_struct) which has all information of process, In case of fork() it will have child's contexts as well. Means pointers to child's PCB.
  • Since pipe ie int fd[2] is created on stack of parent, then duplicated to child's stack. When child exits, its PCB is cleared, PCB of parent is updated and Parent knows there's no one connected at other end of pipe.