4

I found the following code works differently in macOS and Linux:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void catcher( int sig ) {
    printf( "Signal catcher called for signal %d\n", sig );
}

int main( int argc, char *argv[] ) 
{
    struct sigaction sigact;
    sigset_t waitset;
    int sig;
    int result = 0;

    sigemptyset( &sigact.sa_mask );
    sigact.sa_flags = 0;
    sigact.sa_handler = catcher;

    sigaction( SIGINT, &sigact, NULL );

    sigemptyset( &waitset );
    sigaddset( &waitset, SIGHUP);

    result = sigwait(&waitset, &sig) ;
    if(result == 0)
    {
        printf( "sigwait() returned for signal %d\n", sig );
    }
}

When run on macOS and a SIGINT is sent to the process, its handler is executed only after a SIGHUP is sent (thus causing sigwait() to return). In other words it looks sigwait() blocks all signals outside its waiting mask during its wait. When the same program is run on Linux, SIGINT is delivered, that is the handler is run, as soon as a SIGINT is sent to the process. Therefore it looks in Linux sigwait() does not block the signals outside its waiting mask. Which is the standard behaviour? SUSv3 does not make it clear.

diciotto
  • 69
  • 3
  • Can I ask you to `#include ` and SIG_BLOCK that SIGINT before calling `sigwait`? (Neither subtracts from the strangeness you identify.) – pilcrow Nov 25 '19 at 23:10
  • Obviously `signal.h` **was** included (the line just slipped away in the copy, otherwise it would't have compiled). Adding the block of SIGINT simply works as expected in macOS. That is, SIGINT **is** blocked and, after a SIGHUP, the program exit regularly. – diciotto Nov 25 '19 at 23:17
  • Sorry, I mistyped that latter part — I mean SIG_BLOCK that SIGHUP. Using `sigwait` for non-blocked signals is explicitly undefined. – pilcrow Nov 25 '19 at 23:28
  • I am not sure to understand: SUS says "The signals defined by set shall have been blocked at the time of the call to sigwait(); otherwise, the behavior is undefined." The problem presented here is with signals **not** in the set passed to sigwait(). – diciotto Nov 25 '19 at 23:58

2 Answers2

1

sigwait is clearly not specified to block any signals. If it's doing so on MacOS X, this seems to be a bug.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • That was my impression too. Thanks – diciotto Nov 25 '19 at 22:56
  • From the comments on the question, it looks like the behavior is actually undefined if you haven't blocked the signals you're waiting on. So it's theoretically possible you're hitting this UB. But there's no requirement to block signals you're not waiting on. – R.. GitHub STOP HELPING ICE Nov 26 '19 at 00:54
  • @R.., Upon reflection and testing, I don't think OS X is blocking, but instead suspending. See my answer for the reasoning. – pilcrow Nov 26 '19 at 14:57
  • @pilcrow: That's not how it works. Disallowing EINTR doesn't mean signals are deferred while the function executes. Just that it continues running after the signal handler returns. This is all specified in the signals section of XSH, before the individual functions. – R.. GitHub STOP HELPING ICE Nov 26 '19 at 15:15
1

OS X isn't blocking the extraneous signals, but is instead suspending the process à la SIGSTOP.

I find this a reasonable implementation of the specification (IEEE Std 1003.1-2017), which requires that sigwait(set, &s) suspend the calling thread until at least one signal in set becomes pending.

Just like a true STOP, OS X keeps the unblockable SIGKILL will at bay until the process resumes. ps can see a difference between STOP and sigwait, and of course the process resumption is different for each (CONT vs. set), but they're essentially the same process state.

Linux, on the other hand, seems to me to pretend that sigwait is interruptible, and further that user-defined actions were installed with SA_RESTART. User-defined handlers are invoked, and KILL is respected immediately. This behavior is, in my opinion, much more useful, but not to spec — the thread is simply not suspended if it can execute a user-defined signal handler.

Of course, this circumstance is a bit contrived, as sigwait is really designed with multithreaded programs in mind.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
pilcrow
  • 56,591
  • 13
  • 94
  • 135
  • That's not how it works. Disallowing EINTR doesn't mean signals are deferred while the function executes. Just that it continues running after the signal handler returns. This is all specified in the signals section of XSH, before the individual functions. – R.. GitHub STOP HELPING ICE Nov 26 '19 at 15:15
  • *the thread is simply not suspended if it can execute a user-defined signal handler.* Unfortunately, POSIX provides no definition of "suspend". See [**3. Definitions**](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html) – Andrew Henle Nov 26 '19 at 16:56