So our workaround for making usleep thread-safe mostly worked in that
it fixed one of the things making it thread-unsafe, but it seems that
sigsuspend, which usleep uses, is also not thread-safe, since
sometimes one of our two threads would freeze, stuck inside
sigsuspend. So instead of usleep, we're using a trick that Stacken
member map suggested: using select. Calling select with no file
descriptors to work on will have it block on the kernel level until
its timeout is hit. This acts as a sleep. As it turns out, this seems
to completely solve our sleep-related problems, and massively reduces
the amount of audio buffer underruns as a nice bonus!
On Solaris, usleep is not thread-safe and will introduce a race
condition when used in a multithreaded environment. This manifests as
SIGALRM signals escaping from its internal call to sigsuspend. If a
signal is missing a handler, the process dies upon receiving it,
crashing.
The Solaris libc code does something rougly equivalent to this:
PreserveSignalHandlers(...);
/* Effectively NOPs out SIGALRM */
DisableSIGALRM(...);
SetTimer(...);
/* Suspends the thread until it receives a SIGALRM signal. When it
receives this signal, it both wakes up *and* receives the
signal. In this case we don't want to do anything but wake up,
so the SIGALRM handler is a NOP.
*/
sigsuspend(...);
RestoreSignalHandlers(...);
return;
The consequences of this code in a multithreaded context is that if
two sleeps are in progress at the same time, one will necessarily
finish before the other, *restore the non-existant handlers*, and go
on doing its thing. When the other sleep finishes, it won't have a NOP
handler to call when waking up, a handler is searched for, none is
found, and thus we crash.