Search code examples
cmultithreadingnamed-pipesfifo

C fifo keeps blocked


I'm currently studying multithreading with C, but there is something I don't quite understand with our named pipe excersize.
We are expected to do an implementation of file search system that finds files and adds to a buffer with one process and the second process should take filenames from threads of first one, finds the search query inside that file and returns the position to first process via pipe. I did nearly all of it but i'm confused how to do the communication between two processes.

Here is my code that does the communication:
main.c

void *controller_thread(void *arg) {
    pthread_mutex_lock(&index_mutex);
    int index = t_index++; /*Get an index to thread*/
    pthread_mutex_unlock(&index_mutex);
    char sendPipe[10];
    char recvPipe[10];
    int fdsend, fdrecv;
    sprintf(sendPipe, "contrl%d", (index+1));
    sprintf(recvPipe, "minion%d", (index+1));
    mkfifo(sendPipe, 0666);
    execlp("minion", "minion", sendPipe, recvPipe, (char*) NULL);
    if((fdsend = open(sendPipe, O_WRONLY|O_CREAT)) < 0)
        perror("Error opening pipe");
    if((fdrecv = open(recvPipe, O_RDONLY)) < 0)
        perror("Error opening pipe");
    while(1) {
        char *fileName = pop(); /*Counting semaphore from buffer*/
        if(notFile(fileName))
            break;
        write(fdsend, fileName, strlen(fileName));
        write(fdsend, search, strlen(search));
        char place[10];
        while(1) {
            read(fdrecv, place, 10);
            if(notPlace(place)) /*Only checks if all numeric*/
                break;
            printf("Minion %d searching %s in %s, found at %s\n", index, 
                    search, fileName, place);
        }
    }
}

From the online resources I found, I think this is the way to handle the fifo inside the main. I tried to write a test minion just to make sure it works, so here it is

minion.c

int main(int argc, char **argv) {
    char *recvPipe = argv[1];
    char *sendPipe = argv[2];
    char fileName[100];
    int fdsend, fdrecv;
    return 0;
    fdrecv = open(recvPipe, O_RDONLY);
    mkfifo(sendPipe, 0666);
    fdsend = open(sendPipe, O_WRONLY|O_CREAT);
    while(1) {
        read(fdrecv, fileName, 100);
        write(fdsend, "12345", 6);
        write(fds, "xxx", 4);
    }
    return 0;
}

When I run this way, the threads get blocked and prints no response if I change to O_NONBLOCK to mode of open. Then it prints "Error opening pipe no such device or address" error, so I know that somehow I couldn't open the recvPipe inside minion but I don't know what is the mistake


Solution

  • Among the problems with your code is an apparent misunderstanding about the usage of execlp(). On success, this function does not return, so the code following it will never be executed. One ordinarily fork()s first, then performs the execlp() in the child process, being certain to make the child terminate if the execlp() fails. The parent process may need to eventually wait for the forked child, as well.

    Additionally, it is strange, and probably undesirable, that each process passes the O_CREAT flag when it attempts to open the write end of a FIFO. It should be unnecessary, because each one has just created the FIFO with mkfifo(). Even in the event that mkfifo() fails or that some other process removes it before it can be opened, you do not want to open with O_CREAT because that will get you a regular file, not a FIFO.

    Once you fix the execlp() issue, you will also have a race condition. The parent process relies on the child to create one of the FIFOs, but does not wait for that process to do so. You will not get the desired behavior if the parent reaches its open attempt before the child completes its mkfifo().

    I suggest having the parent create both FIFOs, before creating the child process. The child and parent must cooperate by opening the both ends of one FIFO before proceeding to open both ends of the other. One's open for reading will block until the other opens the same FIFO for writing.

    Or you could use ordinary (anonymous) pipes (see pipe()) instead of FIFOs. These are created open on both ends, and they are more natural for communication between processes related by inheritance.

    In any event, be sure to check the return values of your function calls. Almost all of these functions can fail, and it is much better to detect and handle that up front than to sort out the tangle that may form when you assume incorrectly that every call succeeded.