0

I understand from reading this github issue, that "both the old LinuxThreads library and the new NPTL library implement trylock() in a way that is async-signal safe" but that "MacOSX" does not. Why not or How not? How could pthread_mutex_trylock() not be signal safe?

For instance if I wanted to only receive a signal once and then ignore the subsequent signals for a particular signal, I'd like to do something like:

static pthread_mutex_t single_exit_signal_mutex = PTHREAD_MUTEX_INITIALIZER;
static void sig_quit(int sig) //registered for SIGQUIT, SIGTERM, SIGINT
{
    // Only accept one SIGQUIT, SIGTERM, or SIGINT.
    if (pthread_mutex_trylock(&single_exit_signal_mutex) != 0)
        return;
    //...other cleanup code
}

And this seems to be "safe" in Linux under the NPTL library, but why is it not in the POSIX standard?

Motomotes
  • 4,111
  • 1
  • 25
  • 24
  • Why does it *need* to be? – Jason Apr 21 '23 at 14:34
  • [Because **nothing** in C is safe to call from a signal handler](https://port70.net/~nsz/c/c11/n1570.html#note188): "Thus, a signal handler cannot, in general, call standard library functions." The **only** functions that are safe to call from a signal handler are those that an implementation has taken the effort to make async-signal-safe. – Andrew Henle Apr 21 '23 at 14:36
  • async signal safety is about being able to call the function from an async signal handler – boreddad420 Apr 21 '23 at 14:37
  • @Jason Is there another way to handle a signal only once? – Motomotes Apr 21 '23 at 14:40
  • @boreddad420 It's possible that a `SIGTERM` and `SIGQUIT` could happen asynchronously, like me pressing keys on my keyboard and pressing the power off hardware key at once. – Motomotes Apr 21 '23 at 14:42
  • That github link is from 2005, so NPTL is _barely_ in use. I'd find a more modern reference. Since I've used LinuxThreads back then, and later NPTL, I know the impl has improved (even for NPTL). – Craig Estey Apr 21 '23 at 15:07
  • @AndrewHenle Thanks, I suppose that is the actual answer, the STL must be abstract and allowed such freedom, still it seems the present implementation for most Linux systems can handle `trylock()` safely, so maybe it will one day make it to POSIX `signal-safety(7)`. – Motomotes Apr 21 '23 at 15:37
  • 1
    @Motomotes: with respect to handling a signal only once: when you register a signal handler via `sigaction()`, one of the available flags controls whether that signal will be reset to its default disposition when the handler triggers. Alternatively, on POSIX systems, `signal()` and `sigaction()` are async-signal-safe, so a signal handler can change the disposition for future instances of the signal it is handling. – John Bollinger Apr 22 '23 at 14:14

2 Answers2

1

Is there another way to handle a signal only once?

You could use an atomic variable instead of mutex.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • I was going to do this with `sig_atomic_t` and have another thread that polled the variable, but to avoid polling I used a pipe instead, I guess a flag would be useful for some interrupts like `SIGWINCH` so that the screen size could be updated upon the next output by checking a `screensz_chg` flag or things like that, but I wanted to act immediately and once so used a pipe. – Motomotes Apr 23 '23 at 01:59
1

So it seems trylock is not officially supported in signal handlers because "nothing in C is safe to call from signal handlers" as Andrew put it in the comments to the question. I suppose this is due to the interruptible nature of a signal handler. The ideal signal handler would return ASAP, not performing long running operations. So, I decided on using a pipe to another thread as write is signal safe. Here is an example of that implementation which could replace the use of trylock:

#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
#include <csignal>

#define READ_BUF_SET_BYTES(fd, buffer, numb, bytesRead){  \
    ssize_t rb = 0;                                       \
    ssize_t nb;                                           \
    while (rb < numb) {                                   \
        nb = read(fd,(char*)&buffer + rb,numb - rb);              \
        if(nb<=0)                                         \
            break;                                        \
        rb += nb;                                         \
    }                                                     \
    bytesRead = rb;                                       \
}
#define READ_BUF(fd, buffer, numb) {                   \
        ssize_t bytesRead = 0;                         \
        READ_BUF_SET_BYTES(fd, buffer, numb, bytesRead)\
}

#define WRITE_BUF(fd,buf,sz){                  \
    size_t nb = 0;                             \
    size_t wb = 0;                             \
    while (nb < sz){                           \
        wb = write(fd, &buf + nb, sz-nb);      \
        if(wb == EOF) break;                   \
        nb += wb;                              \
    }                                          \
}

#define UNEXPECTED_ERROR (-1)

static int signalPipe[2];

static pthread_mutex_t signal_mutex;

static void sig_ignore(int) {} //SIGHUP
static void sig_quit(int sig) //SIGQUIT, SIGTERM, SIGINT
{
    WRITE_BUF(signalPipe[1],sig,sizeof(int))
}

static void *signalQueueThread(void*){
    pthread_detach(pthread_self());
    //only accepts one signal, which should be a SIGQUIT, SIGINT, or SIGTERM
    int sig = 0;
    while(sig == 0) {
        READ_BUF(signalPipe[0], sig, sizeof(int))
    }

    bool locked = !pthread_mutex_trylock(&signal_mutex);
    if (locked) {
       //...
    }
    return nullptr;
}

int main(int argc, char *argv[]) {
    if (pipe(signalPipe) == UNEXPECTED_ERROR) {
        exit(UNEXPECTED_ERROR);
    }

    pthread_t signalQueueThreadID;
    if (pthread_create(&signalQueueThreadID, nullptr,
                       &signalQueueThread, nullptr) != 0) {
        exit(UNEXPECTED_ERROR);
    }

    signal(SIGQUIT, sig_quit);
    signal(SIGTERM, sig_quit);
    signal(SIGINT, sig_quit);
    signal(SIGHUP, sig_ignore);
    //...
    return 0;
}
Motomotes
  • 4,111
  • 1
  • 25
  • 24