First, I am aware that the mutexes are not considered async-safe normally. This question concerns the use of sigprocmask
to make mutexes safe in a multithreaded program with async signals and signal handlers.
I have some code conceptually like the following:
struct { int a, b; } gvars;
void sigfoo_handler(int signo, siginfo_t *info, void *context) {
if(gvars.a == 42 || gvars.b == 13) {
/* run a chained signal handler */
}
}
/* called from normal code */
void update_gvars(int a, int b) {
gvars.a = a;
gvars.b = b;
}
gvars
is a global variable which is too large to fit in a single sig_atomic_t
. It is updated by normal code and read from the signal handler. The controlled code is a chained signal handler, and so it must run in signal handler context (it may use info
or context
). Consequently, all accesses to gvars
have to be controlled via some sort of synchronization mechanism. Complicating matters, the program is multithreaded, and any thread may receive a SIGFOO
.
Question: By combining sigprocmask
(or pthread_sigmask
) and pthread_mutex_t
, is it possible to guarantee synchronization, using code like the following?
struct { int a, b; } gvars;
pthread_mutex_t gvars_mutex;
void sigfoo_handler(int signo, siginfo_t *info, void *context) {
/* Assume SIGFOO's handler does not have NODEFER set, i.e. it is automatically blocked upon entry */
pthread_mutex_lock(&gvars_mutex);
int cond = gvars.a == 42 || gvars.b == 13;
pthread_mutex_unlock(&gvars_mutex);
if(cond) {
/* run a chained signal handler */
}
}
/* called from normal code */
void update_gvars(int a, int b) {
sigset_t set, oset;
sigemptyset(&set);
sigaddset(&set, SIGFOO);
pthread_sigmask(SIG_BLOCK, &set, &oset);
pthread_mutex_lock(&gvars_mutex);
gvars.a = a;
gvars.b = b;
pthread_mutex_unlock(&gvars_mutex);
pthread_sigmask(SIG_SETMASK, &oset, NULL);
}
The logic goes as following: within sigfoo_handler
, SIGFOO
is blocked so it cannot interrupt the pthread_mutex_lock
. Within update_gvars
, SIGFOO
cannot be raised in the current thread during the pthread_sigmask
-protected critical region, and so it can't interrupt the pthread_mutex_lock
either. Assuming no other signals (and we can always block any other signals that could be problematic), the lock/unlocks should always proceed in a normal, uninterruptible fashion on the current thread, and the use of lock/unlock should ensure that other threads don't interfere. Am I right, or should I avoid this approach?
I found this paper https://www.cs.purdue.edu/homes/rego/cs543/threads/signals.pdf that discusses running AS-unsafe code in sig handlers safely by
This approach satisfies the part of the POSIX standard that says that calling AS-unsafe functions in sig-handlers is only deemed unsafe if the sighandler interrupts an AS-unsafe function (http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03_03 : scroll down to the 1st paragraph after the function list)
I think what you're toying with here is essentially a more fine-grained version of this idea, since you're not trying to prevent
pthread_mutex_lock(&gvars_mutex);
int cond = gvars.a == 42 || gvars.b == 13;
pthread_mutex_unlock(&gvars_mutex);
run from a sig-handler from clashing with any AS-unsafe code but rather just with this same/similar AS-unsafe code dealing with this mutex and these variables.
Unfortunately, POSIX only seems to have a code-only concept of signal-safety: a function is either safe or unsafe, regardless of its arguments.
However, IMO, a semaphores/mutex has no good reason to operate on any data or OS handles other than those contained in the mutex/semaphore they're passed, so I think calling sem_wait(&sem)
/pthread_mutex_lock(&mx);
from a signal handler ought to be safe if it's guaranteed to never clash with a sem_wait
/pthread_mutex_lock
to the same mutex, even though the POSIX standard technically says it shouldn't be safe (counter-arguments more than welcome).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With