spraff
spraff

Reputation: 33435

How do I "disengage" from `accept` on a blocking socket when signalled from another thread?

I am in the same situation as this guy, but I don't quite understand the answer.

The problem:

The solution:

what you should do is send a signal to the thread which is blocked in accept. This will give it EINTR and it can cleanly disengage - and then close the socket. Don't close it from a thread other than the one using it.

I don't get what to do here -- when the signal is received in Thread 1, accept is already blocking, and will continue to block after the signal handler has finished.

  1. What does the answer really mean I should do?
  2. If the Thread 1 signal handler can do something which will cause accept to return immediately, why can't Thread 2 do the same without signals?
  3. Is there another way to do this without signals? I don't want to increase the caveats on the library.

Upvotes: 6

Views: 1881

Answers (5)

Steve Emmerson
Steve Emmerson

Reputation: 7832

I believe signals can be used without increasing "the caveats on the library". Consider the following:

#include <pthread.h>
#include <signal.h>
#include <stddef.h>

static pthread_t             thread;
static volatile sig_atomic_t sigCount;

/**
 * Executes a concurrent task. Called by `pthread_create()`..
 */
static void* startTask(void* arg)
{
    for (;;) {
        // calls to `select()`, `accept()`, `read()`, etc. 
    }
    return NULL;
}

/**
 * Starts concurrent task. Doesn't return until the task completes.
 */
void start()
{
    (void)pthread_create(&thread, NULL, startTask, NULL);
    (void)pthread_join(thread);
}

static void noop(const int sig)
{
    sigCount++;
}

/**
 * Stops concurrent task. Causes `start()` to return.
 */
void stop()
{
    struct sigaction oldAction;
    struct sigaction newAction;

    (void)sigemptyset(&newAction.sa_mask);
    newAction.sa_flags = 0;
    newAction.sa_handler = noop;
    (void)sigaction(SIGTERM, &newAction, &oldAction);

    (void)pthread_kill(thread, SIGTERM); // system calls return with EINTR

    (void)sigaction(SIGTERM, &oldAction, NULL); // restores previous handling

    if (sigCount > 1) // externally-generated SIGTERM was received
        oldAction.sa_handler(SIGTERM); // call previous handler

    sigCount = 0;
}

This has the following advantages:

  • It doesn't require anything special in the task code other than normal EINTR handling; consequently, it makes reasoning about resource leakage easier than using pthread_cancel(), pthread_cleanup_push(), pthread_cleanup_pop(), and pthread_setcancelstate().
  • It doesn't require any additional resources (e.g. a pipe).
  • It can be enhanced to support multiple concurrent tasks.
  • It's fairly boilerplate.
  • It might even compile. :-)

Upvotes: 1

spraff
spraff

Reputation: 33435

Call shutdown() from Thread 2. accept will return with "invalid argument".

This seems to work but the documentation doesn't really explain its operation across threads -- it just seems to work -- so if someone can clarify this, I'll accept that as an answer.

Upvotes: 1

user207421
user207421

Reputation: 311028

Just close the listening socket, and handle the resulting error or exception from accept().

Upvotes: 0

mark4o
mark4o

Reputation: 60933

Instead of blocking in accept(), block in select(), poll(), or one of the similar calls that allows you to wait for activity on multiple file descriptors and use the "self-pipe trick". All of the file descriptors passed to select() should be in non-blocking mode. One of the file descriptors should be the server socket that you use with accept(); if that one becomes readable then you should go ahead and call accept() and it will not block. In addition to that one, create a pipe(), set it to non-blocking, and check for the read side becoming readable. Instead of calling close() on the server socket in the other thread, send a byte of data to the first thread on the write end of the pipe. The actual byte value doesn't matter; the purpose is simply to wake up the first thread. When select() indicates that the pipe is readable, read() and ignore the data from the pipe, close() the server socket, and stop waiting for new connections.

Upvotes: 5

Amardeep AC9MF
Amardeep AC9MF

Reputation: 19064

The accept() call will return with error code EINTR if a signal is caught before a connection is accepted. So check the return value and error code then close the socket accordingly.

If you wish to avoid the signal mechanism altogether, use select() to determine if there are any incoming connections ready to be accepted before calling accept(). The select() call can be made with a timeout so that you can recover and respond to abort conditions.

I usually call select() with a timeout of 1000 to 3000 milliseconds from a while loop that checks for an exit/abort condition. If select() returns with a ready descriptor I call accept() otherwise I either loop around and block again on select() or exit if requested.

Upvotes: 2

Related Questions