sigwait duplicated and transformed signals on MacOS

Viewed 103

I am seeing inconsistent signaling between two threads with the posix pthread/signaling api.

Here is my test program

#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void *thr_fn(void *arg)
{
    int err, signo;
    sigset_t    mask;

   sigemptyset(&mask);
   sigaddset(&mask,SIGUSR1);
   sigaddset(&mask,SIGUSR2);
   sigaddset(&mask,SIGABRT);

    printf("Thread Checker\n");
    
    while(1) {
        err = sigwait(&mask, &signo);
        if (err != 0)
        {
            printf("sigwait failed %d\n",err);
            exit(1);
        }
        
        switch(signo)
        {
            case SIGUSR1:
                printf("SIGUSR1\n");
                break;
            case SIGUSR2:
                printf("SIGUSR2\n");
                break;
            case SIGABRT:
                printf("SIGABRT\n");
                break;
            default:
                printf("Signal %d\n",signo);
                break;
        }
    }
}

int main(void)
{
    int         err;
    pthread_t   tid;
    sigset_t mask;
    
    sigemptyset(&mask);
    sigaddset(&mask,SIGUSR1);
    sigaddset(&mask,SIGUSR2);
    sigaddset(&mask,SIGABRT);
    pthread_sigmask(SIG_BLOCK,&mask,NULL);
    
    err = pthread_create(&tid, NULL, thr_fn, 0);
    if (err != 0)
    {
        printf("can't create thread %d",err);
        exit(1);
    }

    sleep(1);

    for(int x=0;x<5;x++)
    {
        printf("set %d\n",x);
        usleep(100000);
        // raise(SIGUSR1);
        pthread_kill(tid, SIGUSR1);
        pthread_kill(tid, SIGUSR2);
        pthread_kill(tid, SIGABRT);
        usleep(500000);
        printf("\n");
    }
    
    printf("Done\n");
    exit(0);    
}

What I expect to see is 5 groups of identified signals similar to below:

set 1
SIGUSR1
SIGUSR2
SIGABRT

I expect to see 1 representative for each signal but I suppose it is not reasonable to expect the signals to be in order.

$ cc -pthread main.c
$ ./a.out
Thread Checker
set 0
SIGUSR1
SIGABRT
SIGUSR2

set 1
SIGUSR2
SIGABRT
SIGUSR2

set 2
SIGUSR1
SIGABRT
SIGUSR2

set 3
SIGUSR1
SIGABRT
SIGUSR2

set 4
SIGUSR1
SIGABRT
SIGUSR2

Done
Program ended with exit code: 0

Note that set 1 has 2 SIGUSR2's in it. Each time I run the program I frequently a different number of signals. Using the commented out raise(SIGUSR1) in place of pthread_kill(tid,SIGUSR1) does not help.

So the questions is what is happening with SIGWAIT? Why is it possible to for signals to change type, or get duplicated in the signal queue. Why is this not consistent behavior? We see this work 100% in Linux but it also behaves badly in WSL.

2 Answers

I added:

void dummy(int sig) {
    dprintf(1, "Dummy %d\n", sig);
}

and:

signal(SIGUSR1, dummy);
signal(SIGUSR2, dummy);
signal(SIGABRT, dummy);

near the top of main; and it works as expected, and dummy is never invoked. In man sigwait:

The signals specified by set should be blocked, but not ignored, at the time of the call to sigwait().

update: if you change this to fork into thr_fn instead of pthread_create, it works without establishing the dummy params; which leads to a hypothoguess: macos diddles the SIG_DFL when a thread is created. This isn't inconsistent with the mess that pthreads+signals are. If you are planning to use both of these paradigms, you might want to hit pause....

My best guess is that you're running the program under debugger on macOS when you observe the odd behavior. Try running it directly from terminal without debugger.

Related