tomKPZ
tomKPZ

Reputation: 837

Fixing race condition when sending signal to interrupt system call

I have a thread that read()s from a socket, and I want to be able to stop the thread asynchronously. The thread pseudocode looks like:

int needs_quit = 0;

void *thread_read(void *arg)
{
    while(1)
    {
        if(needs_quit)
        {
            close(sock_fd);
            return NULL;
        }
        int ret = read(sock_fd ...);
        if(ret == EINTR) //we received an interrupt signal to stop
        {
            close(sock_fd);
            return NULL;
        } 
        //do stuff with read data
    }
}

The signal handler simply sets needs_quit to 1. Most of the time, this code will work. But, if a signal arrives after if(needs_quit) and before read(sock_fd ...), then read() will not be interrupted, and the thread will never stop. What is the best solution for this problem?

I should point out that I managed to write a working solution with pthread_cancel, but as it turns out, I'm not allowed to use that due to compatibility issues.

Thanks in advance for any feedback.

Upvotes: 2

Views: 672

Answers (2)

pilcrow
pilcrow

Reputation: 58534

As you discovered, flipping a flag isn't enough. There are several approaches I can think of.

Cancel the thread

No flag needed.

However, pthread_cancel is not applicable to your environment, as you noted, and many consider it easy to misuse.

Time-limit the blocking read()

Keep the flag, and be minimally patient.

Replacing the read with a select or poll with a short timeout may be good enough. Do you care if the thread quits "instantly" or within a few seconds?

close or shutdown the socket from a signal handler

No flag, but a signal handler that simply close()s or shutdowns the socket and thus wakes up the read.

Closing fds can be dangerous in a multithreaded environment, as the fd can be immediately re-used by another thread's open or pipe or more exotic calls. It'd be bad, for instance, to have the signal handler close fd 5 while another thread makes a pipe using fd 5 for the readable end, just prior to your read. As @R.. mentions, shutdown may or may not work, but, if it does, is safe here.

Use the self-pipe trick

No flag. Instead your signal handler writes a byte to a non-blocking pipe, and your thread selects on both the interesting socket and the readable end of the pipe. If data arrives (or is already waiting) on the latter, the thread knows it has been signalled.

Use pselect

Can use flag.

A functional, atomic pselect and thoughtful signal masking will give you reliable EINTR indications of signal delivery. Warning: early versions of this call in glibc were not atomic.

Upvotes: 2

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215193

There are basically two general solutions I know for this sort of problem, short of using thread cancellation:

  1. Repeatedly send the signal, with exponential backoff so that you don't keep the target thread from getting scheduled, until the target thread responds that it got the signal.

  2. Instead of an interrupting do-nothing signal handler, use a signal handler which calls longjmp or siglongjmp. Since this results in undefined behavior if you interrupt an async-signal-unsafe function, you need to use pthread_sigmask to keep the signal blocked except at times when it's appropriate to act on the signal. The jmp_buf or sigjmp_buf that you jump (or a pointer to it) should lie in thread-local storage so that the signal handler has access to the one for the correct thread.

In addition, for your specific case of reading from a socket, there may be other approaches that work:

  1. Instead of calling read directly, first wait for the socket to become readable using pselect, which can atomically unblock signals together with waiting. Unfortunately, this precludes using file descriptors whose values exceed FD_SETSIZE. On Linux, the ppoll function avoids this problem, but it's non-standard. Once pselect or ppoll has determined that the socket is readable, read will not block (unless another thread is able to steal the input first). Another variant on this method is to use the self-pipe trick with plain poll (which is portable).

  2. Call shutdown on the socket. You would have to test whether this reliably makes the read fail on systems you care about, since I don't think it's specified clearly, but this is very likely to work and clean and simple (assuming you don't need the socket anymore after cancelling the operation).

Upvotes: 1

Related Questions