6

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

The problem:

  • Thread 1 calls accept on a socket, which is blocking.
  • Thread 2 calls close on this socket.
  • Thread 1 continues blocking. I want it to return from accept.

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.
Community
  • 1
  • 1
spraff
  • 32,570
  • 22
  • 121
  • 229

5 Answers5

5

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.

mark4o
  • 58,919
  • 18
  • 87
  • 102
  • I have discovered `shutdown` (see my own answer). Thanks for this idea, but it seems needlessly complicated and hacky. – spraff May 08 '13 at 01:09
  • Well I don't think so as shutdown won't work on a socket that is not connected. – thodg Jun 23 '17 at 19:10
2

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.

Amardeep AC9MF
  • 18,464
  • 5
  • 40
  • 50
  • So if any signal is sent to the thread, it returns? Is there a no-op signal for this sort of thing? – spraff May 07 '13 at 18:51
  • I believe so. The actual signal type affects the handler's action but many blocking system calls will abort to give you the opportunity to do exactly what you are looking to do. Set up your own signal handler to catch a specific signal and send that from the controlling thread. Your handler doesn't need to do anything in particular. SIGINFO might not be a bad choice. It's fairly benign. – Amardeep AC9MF May 07 '13 at 18:56
  • Seems to contradict the advice [here](http://stackoverflow.com/questions/14613409/are-stdsignal-and-stdraise-thread-safe) ... any thoughts? – spraff May 07 '13 at 19:00
  • True, signal mechanism is not great, can be avoided, and at times will not work in the presence of threads. However, being careful, you can make it work for something like you are talking about, which is the release of a blocked call. The problem comes in when you want to target specific threads and not others in a process, etc. There is also another way to deal with your situation... I'm about to edit my answer to offer that alternative. – Amardeep AC9MF May 07 '13 at 19:04
1

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.

spraff
  • 32,570
  • 22
  • 121
  • 229
  • You mean call `shutdown()` on the listening socket? – user207421 May 08 '13 at 01:27
  • POSIX requires `shutdown()` to fail on a socket that is not connected, and the Linux documentation agrees with this. If you are seeing different behavior it would be unwise to rely on it, as it is likely to vary across systems and perhaps kernel versions. – mark4o May 08 '13 at 15:29
0

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

user207421
  • 305,947
  • 44
  • 307
  • 483
  • @mark4o On my Linux system, `select ()` doesn't return if the socket is closed on another thread. – Steve Emmerson May 08 '15 at 02:53
  • @SteveEmmerson: that's why my answer suggests using a pipe to tell the thread in `select()` to do the close, instead of closing it in the other thread. – mark4o May 08 '15 at 21:03
0

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. :-)
Steve Emmerson
  • 7,702
  • 5
  • 33
  • 59