1

I'm trying to create named pipe with O_NONBLOCK mode and listen for a read event using "SELECT" method in a separate thread. There is a problem when i'm trying to close the program after some sleeping time in the main thread. I expect that when the named pipe's file descriptor is closed using close method, the select operation should immediately stop and return some value. But unfortunately, the select operation has no reaction when the file descriptor is closed and the thread which executes select method just hangs...

Any ideas how to solve it? The example code is below.

#include <pthread.h>
#include <limits.h>
#include <cctype>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <exception>
#define BUFFER PIPE_BUF
#define LPVOID void *
#define BOOL int
#define TRUE 1
#define CONST const
#define CHAR char

class CPipeTest
{
    public:

    int fd;
    int nfd;
    fd_set rfd;
    pthread_t t;

    CPipeTest() {};
    ~CPipeTest() {};

    static LPVOID fnExecuteThread(LPVOID lpParam)
    {
        ((CPipeTest*)lpParam)->fnRunThread();
        return NULL;
    }

    BOOL fnRunThread()
    {
        printf("Going to listen...\r\n");
        select(nfd, &rfd, NULL, NULL, NULL);
        printf("Close listener...\r\n");
        return TRUE;
    }

    void fnInit()
    {
        CONST CHAR * name = "./test_fifo1";
        mkfifo(name, 0777);
        fd = open(name, O_NONBLOCK | O_RDONLY);
        nfd = fd + 1;
        FD_ZERO(&rfd);
        FD_SET(fd, &rfd);

        pthread_create( &t, NULL, fnExecuteThread, (LPVOID)this);

        sleep(30);
        printf("Close file descriptor - listener should be closed automatically and immediately\r\n");
        close(fd);
        printf("Descriptor closed wait for thread to to be closed\r\n");
        pthread_join(t, NULL);
        printf("Thread is closed - everything is fine\r\n");
    }

};

int main()
{
    CPipeTest pi;
    pi.fnInit();

    return 0;

}
vdm
  • 185
  • 1
  • 4
  • 16
  • very related: http://stackoverflow.com/questions/543541/what-does-select2-do-if-you-close2-a-file-descriptor-in-a-separate-thread – Nate Kohl Jan 14 '13 at 21:53
  • 1
    I have added sleep here just to demonstrate the problem that select method does not exit when the name pipe file descriptor is closed – vdm Jan 14 '13 at 21:54

3 Answers3

0

Try to avoid writing to a variable in one thread, and reading from it in another.

And by avoid, I mean don't do that. :)

Every variable that fnRunThread uses should only be touched by that function unless you are synchronizing access to it.

Here you have someone doing what you did: What does select(2) do if you close(2) a file descriptor in a separate thread? and the undefined behavior is pointed out.

One good way to fix this is to have a "stop reading" command you can pass over your fifo. When the reading thread gets it, it proceeds to stop reading, then it closes the file. (note that the reading thread closes the file handle -- if the reading thread is reading from the file handle, that means it owns it. Barring some additional synchronization, once you launch the thread, you should only read from or write to variables that are owned by the thread in the thread, and should not read from or write to these variables outside of the thread).

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • [Expletives are not acceptable behavior on StackOverflow](http://meta.stackexchange.com/questions/22232/are-expletives-allowed-on-se-sites). – Adam Rosenfield Jan 14 '13 at 21:58
  • 2
    In all fairness, the variables are set before creating the thread and then only read; that's fine. – Alex Chamberlain Jan 14 '13 at 21:58
  • `rfd` contains `fd` (in `FD_SET(fd, &rfd)`), which is closed in the main thread after the `pthread_create`. So the thread is reading `rfd`, and hence from `fd` when the main thread writes to it by closing it. – Yakk - Adam Nevraumont Jan 14 '13 at 22:02
  • @Yakk That's not how file descriptors and fd_sets work. an fd_set just contains a bit indicating whether the numeric value of a file descriptor is a member of that set. FD_SET(fd, &rfd) does not make `fd` and that descriptor inside `rfd` the same thing from a thread safety viewpoint. The code is anyway for demonstration for a question, so it's fine. – nos Jan 14 '13 at 22:08
0

There should be two file descriptors, one for reading and another for writing.

For blocking mode, the one for writing (which you will close in the initial thread) should be opened in the initial thread, after starting the "reader" thread. The one for reading should be opened in the reader thread. For non-blocking mode, it may be done in the same thread, if you first open for reading, then for writing (or ENXIO will be returned for opening writer with no reader).

When you close the writing side, the reading side will receive notification with select. (If there would be a real data exchange, the following read would read zero bytes, that's how you detect EOF).

If you switch to anonymous pipes, you'll get a pair of descriptors from the pipe call automatically.

Anton Kovalenko
  • 20,999
  • 2
  • 37
  • 69
  • But there can be situation, when the only reading side was open and writing side did not start for some reason... How to close named pipe in this case when the program should be closed? – vdm Jan 14 '13 at 22:05
  • @vdm in that case you'll have to employ a timer. If nothing happens on a descriptor in a given time, assume stuff have gone wrong and take action. – nos Jan 14 '13 at 22:11
  • Create a dummy writer and close it instantly (that is, if you absolutely have to do it this way. I strongly recommend to avoid such situation by opening two descriptors at once (incidentally, that's what `pipe()` does) before any thread is started) – Anton Kovalenko Jan 14 '13 at 22:16
  • Huuh, it does not sound good... Well if it is the only way then i got it. Although such solution disappointed me. I thought that the named pipe behaves the same as socket, which receive the event if the remote side is closed. – vdm Jan 14 '13 at 22:30
  • @vdm it does, when you have *two sides* (which you should -- that's the point of my answer). – Anton Kovalenko Jan 14 '13 at 22:31
0

The problem is a FIFO (a special type of file) has to be open at both ends before you can do anything with it. It doesn't make sense you close the same end and expect the other thread to react.

The following code should do what you want:

    ....
    nfd = fd + 1;
    FD_ZERO(&rfd);
    FD_SET(fd, &rfd);

    int write_fd = open(name, O_WRONLY);   // open the other end
    pthread_create( &t, NULL, fnExecuteThread, (LPVOID)this);

    sleep(30);
    printf("Close file descriptor - listener should be closed automatically and immediately\r\n");
    close(write_fd);                       // close the other end
    ....