splunk
splunk

Reputation: 6805

Check if pipe is empty, UNIX

I have 2 processes, the parent and the child. Parent process should wait for the user to insert some string and then it should send this string to the child process through a pipe. I've successfully done this, but what I'm trying to achieve is to check if the pipe is empty, if it is empty for 60 seconds then the child process should send a signal to its parent.

My Code so far:

int main(void)
{
        int     fd[2], nbytes;
        pid_t   childpid;
        char    string[100];
        char    readbuffer[80];
        pipe(fd);

        if((childpid = fork()) == -1)
        {
                perror("fork");
                exit(1);
        }
        else if(childpid > 0) //parent process
        {
                close(fd[0]);
                printf ("Insert a string: ");
                scanf("%s",string);
                /* Send "string" through the output side of pipe */
                write(fd[1], string, (strlen(string)+1));
                exit(0);
        }
        else // child process
        {
                close(fd[1]);
                // check if pipe is empty for 60 seconds 
                nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
                printf("Received string: %s", readbuffer);
        }
        return(0);
} 

Upvotes: 1

Views: 2594

Answers (1)

Crowman
Crowman

Reputation: 25926

Here's an example where the parent sleep()s for a random number of seconds and then write()s to the child through the pipe. If the child does not receive the message within 5.5 seconds, it sends SIGUSR1 to the parent, and the parent does not write the message. This repeats for a specified number of times.

Note that the child waits for 5.5 seconds, rather than 5 seconds, because we're using sleep() in the parent which takes whole seconds. This (usually) will leave enough of a time gap to avoid the case where the parent sleep()s and the child waits for 5 seconds, and their wires cross in the middle. We could instead use nanosleep() in the parent, and have the child wait for a whole number of seconds.

Notes:

  1. We can use select() with a timeout in the child process, to wait for the specified amount of time. select() will return the number of ready file descriptors, so if we only check the pipe and it returns 0, we know that nothing is ready for reading in the pipe.

  2. We can use sleep() in the parent to test it out. sleep() returns 0 if it slept for the requested time, and non-zero if it was interrupted by a signal, so we can check the return value to figure out which case it was. For your particular case, if you want the parent to get input from the user, read() will return -1 and set errno to EINTR if it is waiting for user input and receives a signal, so you can detect the timeout in that way. Of course, in this particular use case, it's easier for the parent to just call select() itself on STDIN_FILENO with your 60 second timeout, rather than have the child wait on the pipe and send a signal.

  3. In the child, we have to FD_ZERO the fd_set and repopulate the struct timeval each time through the loop, because select() might modify them.

  4. We need to register a signal handler for SIGUSR1 (which does nothing) to avoid the process being terminated when it is received. We don't need the signal handler to do anything, because sleep() will tell us via the return value when it was interrupted by a signal, and that's all the information we need, here.

Code:

#define _XOPEN_SOURCE 500

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/select.h>

#define READ_END 0
#define WRITE_END 1
#define SEC_THRESHOLD 5
#define LOOP_TIMES 5
#define BUFFER_SIZE 512

void handler(int signum)
{
    (void) signum;  //  Ignore unused argument
}

int main(void)
{
    int p[2];
    if ( pipe(p) == -1 ) {
        perror("pipe() failed");
        exit(EXIT_FAILURE);
    }

    pid_t pid = fork();
    if ( pid == -1 ) {
        perror("fork() failed");
        exit(EXIT_FAILURE);
    }
    else if ( pid == 0 ) {
        if ( close(p[WRITE_END]) == -1 ) {
            perror("failed to close pipe end in child");
            exit(EXIT_FAILURE);
        }

        for ( int i = 0; i < LOOP_TIMES; ++i ) {
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(p[READ_END], &fds);

            struct timeval timeout;
            timeout.tv_sec = SEC_THRESHOLD;
            timeout.tv_usec = 500000;

            printf("Loop %d: child waiting for %d.5 seconds.\n",
                   i, SEC_THRESHOLD);
            int status = select(p[READ_END] + 1, &fds, NULL, NULL, &timeout);
            if ( status == -1 ) {
                perror("select() failed in child");
                exit(EXIT_FAILURE);
            }
            else if ( status == 0 ) {
                printf("Loop %d: timed out in child, sending signal.\n", i);
                kill(getppid(), SIGUSR1);
            }
            else {
                char buffer[BUFFER_SIZE] = {0};
                if ( read(p[READ_END], buffer, BUFFER_SIZE - 1) == -1 ) {
                    perror("read() failed in child");
                    exit(EXIT_FAILURE);
                }
                printf("Loop %d: child read: [%s]\n", i, buffer);
            }
        }

        if ( close(p[READ_END]) == -1 ) {
            perror("failed to close read end of pipe in child");
            exit(EXIT_FAILURE);
        }

        exit(EXIT_SUCCESS);
    }
    else {
        if ( close(p[READ_END]) == -1 ) {
            perror("failed to close pipe end in parent");
            exit(EXIT_FAILURE);
        }

        struct sigaction sa;
        sa.sa_handler = handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        sigaction(SIGUSR1, &sa, NULL);

        srand((unsigned) time(NULL));

        for ( int i = 0; i < LOOP_TIMES; ++i ) {
            const char * msg = "Message to child";
            const int wait_time = rand() % 6 + 3;

            printf("Loop %d: parent waiting for %d seconds...\n", i, wait_time);
            int status = sleep(wait_time);
            if ( status == 0 ) {
                printf("Loop %d: parent sending message to child.\n", i);
                if ( write(p[WRITE_END], msg, strlen(msg)) == -1 ) {
                    perror("write() error in parent");
                    exit(EXIT_FAILURE);
                }
            }
            else {
                printf("Loop %d: parent interrupted by signal.\n", i);
            }
        }

        if ( close(p[WRITE_END]) == -1 ) {
            perror("failed to close write end of pipe in parent");
            exit(EXIT_FAILURE);
        }

    }

    if ( waitpid(pid, NULL, 0) == -1 ) {
        perror("waitpid() failed");
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS;
}

and sample output session:

paul@horus:~/src/sandbox$ ./sigpipe
Loop 0: parent waiting for 7 seconds...
Loop 0: child waiting for 5.5 seconds.
Loop 0: timed out in child, sending signal.
Loop 1: child waiting for 5.5 seconds.
Loop 0: parent interrupted by signal.
Loop 1: parent waiting for 7 seconds...
Loop 1: timed out in child, sending signal.
Loop 2: child waiting for 5.5 seconds.
Loop 1: parent interrupted by signal.
Loop 2: parent waiting for 6 seconds...
Loop 2: timed out in child, sending signal.
Loop 3: child waiting for 5.5 seconds.
Loop 2: parent interrupted by signal.
Loop 3: parent waiting for 5 seconds...
Loop 3: parent sending message to child.
Loop 4: parent waiting for 3 seconds...
Loop 3: child read: [Message to child]
Loop 4: child waiting for 5.5 seconds.
Loop 4: parent sending message to child.
Loop 4: child read: [Message to child]
paul@horus:~/src/sandbox$ 

so we can see that each iteration the parent gets interrupted if the child reaches its 5.5 second timeout, and successfully sends the message in all other cases.

Upvotes: 1

Related Questions