开发者

Wake up thread blocked on accept() call

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 its 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)
{
    pr开发者_运维技巧intf("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();

}


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.


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().


Close the listening socket and accept will return an error.

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


pthread_cancel to cancel a thread blocked in accept() is risky if the pthread implementation does not implement cancellation properly, that is if the thread created a socket, just before returning to your code, a pthread_cancel() is called for it, the thread is canceled, and the newly created socket is leaked. Although FreeBSD 9.0 and later does not have such a race condition problem, but you should check your OS first.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜