read from a pipe is blocked after close writer

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char **argv) {

    int childs[3];
    for (int i = 0; i < 3; ++i) {
        int p[2];
        if (pipe(p) == -1) { perror("pipe"); exit(1); }

        pid_t pid = fork();
        if (pid) {
            close(p[0]);
            childs[i] = p[1];
        }
        else {
            close(p[1]);
            printf("child %d start\n", i + 1);

            char buf[10];
            buf[0] = 0;
            int r;
            if ((r = read(p[0], buf, 9)) == -1) { ... }

            printf("child %d read %s (%d), finish\n", i + 1, buf, r);

            sleep(2);
            exit(0);
        }
    }

    for (int i = 0; i < 3; ++i) {
    //        if (argc > 1) {
    //            write(childs[i], "42", 2);
    //        }
    // ============== HERE >>>
        close(childs[i]);
    }

    pid_t pid;
    while ((pid = waitpid(-1, NULL, 0)) > 0) {
         printf("child %d exited\n", pid);
    }

    return 0;
}

Output with comment:

child 1 start
child 2 start
child 3 start
child 3 read  (0), finish

The next line is displayed after 2 seconds

child 2 read  (0), finish

The next line is displayed after 2 seconds

child 1 read  (0), finish

I do not write to the channel in the parent. Closing it, I want to give a signal to the child that will be waiting in the read.

It seems that there is a following. Сhild N expected finishes reading from the result 0, it's ok. Children 2 (N-1) and 1 are locked in a read to a child 3 is completed. Then the child 1 is similar will wait.

Why lock occur?

Upvotes: 3

Views: 123

Answers (2)

melpomene
melpomene

Reputation: 85897

Child processes inherit open file descriptors from their parent. Your main process opens file descriptors in a loop (using pipe, keeping only the write ends). Child 1 inherits no descriptors (except for stdin/stdout/stderr); child 2 inherits childs[0] (the descriptor going to child 1); child 3 inherits childs[0] and childs[1] (the descriptors going to child 1 and 2).

read on a pipe blocks as long as any write descriptor is still open (because it could be used to send more data). So child 1 waits (because child 2 and child 3 still have an open write descriptor) and child 2 waits (because child 3 still has an open write descriptor); only child 3 sleeps and exits. This causes its file descriptors to close, which wakes up child 2. Then child 2 sleeps and exits, closing its file descriptors, which finally wakes up child 1.


If you want to avoid this behavior, you have to close the open file descriptors in each child:

    else {
        for (int j = 0; j < i; j++) {
            close(childs[j]);
        }
        close(p[1]);
        printf("child %d start\n", i + 1);

Upvotes: 2

Petr Skocik
Petr Skocik

Reputation: 60163

The write ends of the pipes are getting inherited by the children. Since filedescriptor are ref-counted, the write end is only considered closed if all references to it are closed.

Below is your code, slightly refactored, with a fix added:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char **argv) {

    int children_w[3];

    for (int i = 0; i < 3; ++i) {
        int p[2];

        if (0>pipe(p)) 
            { perror("pipe"); exit(1); }

        pid_t pid;
        if(0> (pid= fork()))
            { perror("fork"); exit(1); }

        if(pid==0) {
            /* Fix -- close the leaked write ends  */
            int j;
            for(j=0; j<i; j++)
                close(children_w[j]);
            /* end fix*/ 
            close(p[1]);
            printf("child %d start\n", i + 1);

            char buf[10];
            buf[0] = 0;
            int r;
            if ((r = read(p[0], buf, 9)) == -1) { perror("read");/*...*/ }

            printf("child %d read %s (%d), finish\n", i + 1, buf, r);

            sleep(2);
            exit(0);
        }
        children_w[i] = p[1];
        close(p[0]);
    }

    for (int i = 0; i < 3; ++i) {
    //        if (argc > 1) {
    //            write(childs[i], "42", 2);
    //        }
    // ============== HERE >>>
        close(children_w[i]);
    }

    pid_t pid;
    while ((pid = waitpid(-1, NULL, 0)) > 0) {
         printf("child %d exited\n", pid);
    }

    return 0;
}

Upvotes: 0

Related Questions