Install an empty signal handler to an unused POSIX realtime signal, SIGRTMIN+0
to SIGRTMAX-0
, and use pthread_kill()
to send that signal to the thread blocked in recvmsg()
. Realtime signals are queued (reliable) whereas standard signals are not. This means that interrupting two different threads at the same time works if you use a realtime signal, but may fail if using a normal signal (such as SIGUSR1
).
It is the delivery of a signal to a signal handler, that interrupts a library function or a system call -- even if the body of the signal handler function is empty. (Assuming, of course, that you install the signal handler using sigaction()
without the SA_RESTART
flag. See man 7 signal
, under "Interruption of system calls and library functions by signal handlers" for details.)
The one problem you may encounter with this approach (using a signal to interrupt a blocking I/O function) is when the signal is raised early, before the target thread is actually blocking on the I/O function.
That window cannot really be avoided, you just have to deal with it.
Personally, I like to use a dedicated thread maintaining timeout structures, something like
struct timeout {
pthread_t who;
struct timespec when; /* Using CLOCK_MONOTONIC */
volatile int state;
};
that each thread can acquire and release -- I've found particularly useful that a thread can hold multiple timeouts at the same time -- and a simple atomic function to check the state (to be used in e.g. loop conditions).
The trick is that when the timeout initially triggers, it is not removed, due to the aforementioned problematic time window. Instead, I like to mark the timeout "elapsed", and just rearm it a millisecond of a few milliseconds later. This repeats, until the target thread releases the timeout. (Each time a timeout is triggered, I do send a POSIX realtime signal to the target thread, with an empty signal handler function installed.)
This way, even if your recv()
/send()
loops occasionally miss the signal, they will be notified very soon afterwards.
You will also want to install a thread cleanup handler that releases all timeouts owned by the thread, if you ever cancel threads. Otherwise you may "leak" timeout structures.
If you use clock_gettime()
and pthread_cond_timedwait()
, to wait for a new timeout entries (added by other threads) or an existing timeout to elapse, the timeout management thread will be very lightweight and not consume much memory or CPU time at all. Note that you can use CLOCK_MONOTONIC
, if you create the new-timeouts-added condition variable with an attribute set that has that clock selected via pthread_condattr_setclock()
.