Why does child termination interrupt sigsuspend, even when SIGCHLD is masked?

The code below sets an short-term alarm, fork()s a shorter-lived child, and then calls sigsuspend awaiting only a SIGALRM. I would expect it to die by SIGALRM.


Instead, on OS X, the sigsuspend appears to be interrupted by the child's termination, even though SIGCHLD is masked during the call. That is contrary to my expectation of POSIX behavior and my read of the OS X man page, and contrary to observed behavior on at least Linux and FreeBSD (reported on Stackoverflow).


Catching SIGCHLD or leaving it at SIG_DFL makes no difference, and, indeed, a user-defined signal handler never appears to run in any case. Only "natural" child termination appears to cause this. kill -CHLD $mypid won't do it.


What is going on?


#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

#define ALARM_TIMEOUT 5
#define CHILD_SLEEP   2

static volatile sig_atomic_t saw_chld = 0;

static
void noop(int s, siginfo_t *si, void *c) {
  saw_chld = 1;
}

int
main(int argc, char **argv) {
  alarm(ALARM_TIMEOUT);

  if (argc > 1) {
    printf("parent catching CHLD\n");

    struct sigaction sa = { 0 };
    sa.sa_sigaction = noop;
    sa.sa_flags = SA_SIGINFO;
    sigfillset(&sa.sa_mask);
    (void)sigaction(SIGCHLD, &sa, NULL);
  }

  if (fork() == (pid_t)0) {    // FIXME - error checking
    printf("child sleeping\n");
    sleep(CHILD_SLEEP);
    printf("child exit\n");
    exit(0);
  }

  sigset_t mask;
  sigfillset(&mask);
  sigdelset(&mask, SIGALRM);

  printf("parent suspending\n");
  errno = 0;
  int r = sigsuspend(&mask);
  printf("parent sigsuspend() => %d (errno %d)\n", r, errno);
  if (argc > 1) printf("parent saw SIGCHLD? %d\n", saw_chld);

  exit(0);
}


When I run the above on OS X, I see:


$ ./demo 
parent suspending
child sleeping
child exit
parent sigsuspend() => -1 (errno 4)

$ ./demo catch
parent catching CHLD
parent suspending
child sleeping
child exit
parent sigsuspend() => -1 (errno 4)
parent saw SIGCHLD? 0

That is contrary to my expectation of POSIX behavior

I’m not enough of a Posix ‘language lawyer’ to offer an opinion on that but I do have three bits of advice:

  • If you think that this is not Posix compliant, you should file a bug about that. That way someone more familiar with Posix can look at the issue.

    Please post your bug number, just for the record.

  • I strongly recommend that you avoid things like

    alarm
    and
    fork
    in Apple-specific code. These APIs are needed for Posix compliance, and they’re fine to use if your goal is to get cross-platform code working on the Mac, but their interactions with the rest of macOS are less than smooth.
  • Given the level of detail in your post I strongly suspect that you won’t have problems updating the code in your product to cope with this behaviour. However, if you run into problems feel free to post back with details on what your real code is doing with these features and how this behaviour is causing it problems.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks, Eskimo. Submitted feedback FB7731811.

Submitted feedback FB7731811.

Much appreciated.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Why does child termination interrupt sigsuspend, even when SIGCHLD is masked?
 
 
Q