views:

454

answers:

3

Sockets on Linux question

I have a worker thread that is blocked on an accept() call. It simply waits for an incoming network connection, handles it, and then returns to listening for the next connection.

When it is time for the program to exit, how do I signal this network worker thread (from the main thread) to return from the accept() call while still being able to gracefully exit its loop and handle it's cleanup code.

Some things I tried:

  1. pthread_kill to send a signal. Feels kludgy to do this, plus it doesn't reliably allow the thread to do it's shutdown logic. Also makes the program terminate as well. I'd like to avoid signals if at all possible.

  2. pthread_cancel. Same as above. It's a harsh kill on the thread. That, and the thread may be doing something else.

  3. Closing the listen socket from the main thread in order to make accept() abort. This doesn't reliably work.

Some constraints:

If the solution involves making the listen socket non-blocking, that is fine. But I don't want to accept a solution that involves the thread waking up via a select call every few seconds to check the exit condition.

The thread condition to exit may not be tied to the process exiting.

Essentially, the logic I am going for looks like this.

void* WorkerThread(void* args)
{
    DoSomeImportantInitialization();  // initialize listen socket and some thread specific stuff

    while (HasExitConditionBeenSet()==false)
    {
        listensize = sizeof(listenaddr);
        int sock = accept(listensocket, &listenaddr, &listensize);

        // check if exit condition has been set using thread safe semantics
        if (HasExitConditionBeenSet())
        {
            break;
        }

        if (sock < 0)
        {
            printf("accept returned %d (errno==%d)\n", sock, errno);
        }
        else
        {
            HandleNewNetworkCondition(sock, &listenaddr);
        }
    }

    DoSomeImportantCleanup(); // close listen socket, close connections, cleanup etc..
    return NULL;
}

void SignalHandler(int sig)
{
    printf("Caught CTRL-C\n");
}

void NotifyWorkerThreadToExit(pthread_t thread_handle)
{
    // signal thread to exit
}

int main()
{
    void* ptr_ret= NULL;
    pthread_t workerthread_handle = 0;

    pthread_create(&workerthread, NULL, WorkerThread, NULL);

    signal(SIGINT, SignalHandler);

    sleep((unsigned int)-1); // sleep until the user hits ctrl-c

    printf("Returned from sleep call...\n");

    SetThreadExitCondition(); // sets global variable with barrier that worker thread checks on

    // this is the function I'm stalled on writing
    NotifyWorkerThreadToExit(workerthread_handle);

    // wait for thread to exit cleanly
    pthread_join(workerthread_handle, &ptr_ret);

    DoProcessCleanupStuff();

}
+4  A: 

You can use a pipe to notify the thread that you want it to exit. Then you can have a select() call which selects on both the pipe and the listening socket.

For example (compiles but not fully tested):

// NotifyPipe.h
#ifndef NOTIFYPIPE_H_INCLUDED
#define NOTIFYPIPE_H_INCLUDED

class NotifyPipe
{
        int m_receiveFd;
        int m_sendFd;

    public:
        NotifyPipe();
        virtual ~NotifyPipe();

        int receiverFd();
        void notify();
};

#endif // NOTIFYPIPE_H_INCLUDED

// NotifyPipe.cpp

#include "NotifyPipe.h"

#include <unistd.h>
#include <assert.h>
#include <fcntl.h>

NotifyPipe::NotifyPipe()
{
    int pipefd[2];
    int ret = pipe(pipefd);
    assert(ret == 0); // For real usage put proper check here
    m_receiveFd = pipefd[0];
    m_sendFd = pipefd[1];
    fcntl(m_sendFd,F_SETFL,O_NONBLOCK);
}


NotifyPipe::~NotifyPipe()
{
    close(m_sendFd);
    close(m_receiveFd);
}


int NotifyPipe::receiverFd()
{
    return m_receiveFd;
}


void NotifyPipe::notify()
{
    write(m_sendFd,"1",1);
}

Then select with receiverFd(), and notify for termination using notify().

Douglas Leeder
I'm very curious about this, haven't used pipes very often. Do you have code you could post to demonstrate this? :)
Sam Post
Thank you. I was actually thinking about this approach when I posted the question. It's clean and works great.
selbie
A: 

Close the listening socket and accept will return an error.

What doesn't reliably work with this? Describe the problems you're facing.

Len Holgate
Calling close on a socket will not wakeup a blocking accept call that is on another thread. I tried it on Linux - it doesn't work. Although, I believe this technique works on Windows, which is what I've done to get IOCP threads to wakeup.
selbie
Thanks, I didn't know that...
Len Holgate
+5  A: 

Close the socket using the shutdown() call. This will wake up any threads blocked on it, while keeping the file descriptor valid.

close() on a descriptor another thread B is using is inherently hazardous: another thread C may open a new file descriptor which thread B will then use instead of the closed one. dup2() a /dev/null onto it avoids that problem, but does not wake up blocked threads reliably.

Note that shutdown() only works on sockets -- for other kinds of descriptors you likely need the select+pipe-to-self or cancellation approaches.

jilles
This solution works too. So I'll give Douglas the win since his solution is applicable to other threading problems. But I'll give your answer a "bump" so you can get some points. :)
selbie