1

I have a fairly big application under care. As part of its job it spawns some child processes and needs to monitor their state (running, crashed).

Child process deaths were detected by setting signal handler for SIGCHLD using signal(2). Some time ago I migrated it to signalfd(2). What I did was simple:

  1. removed the signal handler for SIGCHLD
  2. blocked SIGCHLD and created a signalfd(2) to capture SIGCHLD

My problem is that the file descriptor I created does not seem to capture SIGCHLD. However, if I ignore the return value of read(2) call on that descriptor and call waitpid(-1, &status, WNOHANG) I can obtain the information about exited child processes. So it looks like the notification is delivered but my signalfd(2) descriptor just ignores it.

I made sure to have exactly one place in the program where read(2) is called on the signalfd(2) descriptor, exactly one place where waitpid(2) is called, and exactly one place were the signal handling is set up.

The setup code looks like this:

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);

sigprocmask(SIG_BLOCK, &mask, nullptr);

int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (signal_fd == -1) {
    /* log failure and exit */
} else {
    /* log success */
}

The reading code looks like this:

signalfd_siginfo info;
memset(&info, 0, sizeof(info));

if (read(signal_fd, &info, sizeof(info)) == -1) {
    /*
     * Log failure and return.
     * The file descriptor *always* returns EAGAIN, even in
     * presence of dead child processes.
     */
    return;
}

if (info.ssi_signo == SIGCHLD) {
    int status = 0;
    int child = waitpid(-1, &status, WNOHANG);

    /*
     * Process result of waitpid(2). The call is successful even if
     * the read of signalfd above returned an error.
     */
}

What am I doing wrong?

Edit: The problem is that read(2) fails with EAGAIN even if there are dead child processes ready to be waitpid(2)-ed, which means that a SIGCHLD must have been delivered to my master process. I know that read(2) may return EAGAIN for non-blocking file descriptors and the code accounts for that.

1
  • Non-blocking IO is a pain. It looks like a good idea, until you try it. Instead use blocking, and learn how to do event driven with select or poll. Commented Apr 7, 2020 at 17:12

2 Answers 2

1

When migrating from signal handling based on signal(2) or sigaction(2) to signalfd(2) you change the way you receive signals. The old way leaves the signals unblocked, the new one needs them blocked.

If you have some regions of code in which you do not want to be disturbed by signals you need to block them:

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGFOO);
pthread_sigmask(SIG_BLOCK, &mask, nullptr);

{
    /* not-to-be-disturbed code here */
}

This requires that you later unblock them, because otherwise signal(2) or sigaction(2) will not be able to pick them up.

{
    /* not-to-be-disturbed code here */
}

pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);

However, for signalfd(2) the signals must stay blocked. If you have a long neglected code path that you rarely look at and it follows the old way ie, blocks and unblocks some signals, it may trash your signal-reading file descriptor you got from signalfd(2).

TL;DR Vet your code for any calls to signal(2), sigaction(2), pthread_sigmask(2) etc. when migrating to signalfd(2) to check if you did not forget about some codepath that messes with the signal mask.

(Two and a half years later may be a bit late, but maybe the answer will help someone.)

1
  • 1
    It'd help to if the code originally put the temporary blocking and unblocking in separate functions, then it'd be enough to just quench them. Or if those functions supported nested blocking (using a counter, probably), then the main program could just ask for all signals blocked in the start, and the functions would never unblock them.
    – ilkkachu
    Commented Jul 13, 2022 at 12:39
0

Your read(2) returns EAGAIN because you opened the file in non-blocking mode with signalfd(..., SFD_NONBLOCK | ...) (SFD_NONBLOCK is the same as O_NONBLOCK).

Don't open or set file descriptors to non-blocking mode if you want to do blocking reads on them.

11
  • The fact that read(2) returns EAGAIN is actually what I wanted - if I get this errno I just skip further processing. The problem is that EAGAIN is returned even if there are child processes waiting to be waitpid(2)-ed.
    – Mael
    Commented Apr 7, 2020 at 17:25
  • That doesn't matter. You want do a blocking read() -- you turn off O_NONBLOCK. If you turn O_NONBLOCK on -- you should use poll(), select(), epoll(), etc. It's as simple as that.
    – user313992
    Commented Apr 7, 2020 at 17:27
  • 1
    @Mael your code may have of course other problems. If you posted a complete example, you may get a better answer ;-)
    – user313992
    Commented Apr 7, 2020 at 18:07
  • 1
    Yes give an example of the simplest code that works, but exhibits the problem. ( Nearly every time I do this, I find the problem my self). And what can you not afford about blocking? Commented Apr 7, 2020 at 18:08
  • 1
    You should check your assumptions about performance. Always test your assumptions, when optimising code. Commented Apr 7, 2020 at 21:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .