Reputation: 837
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
Reputation: 58534
As you discovered, flipping a flag isn't enough. There are several approaches I can think of.
No flag needed.
However, pthread_cancel
is not applicable to your environment, as you noted, and many consider it easy to misuse.
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 handlerNo flag, but a signal handler that simply close()
s or shutdown
s 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.
No flag. Instead your signal handler writes a byte to a non-blocking pipe, and your thread select
s 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.
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
Reputation: 215193
There are basically two general solutions I know for this sort of problem, short of using thread cancellation:
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.
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:
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).
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