Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blocking new signals while in handler

I have a parent process that manages a child (fork, execve). I created a handler in the parent to catch SIGCHLD signals from the child in order to call waitpid() and take appropriate action such as restarting the child.

I understood from the manual page for sigaction() that, while inside a signal handler, further signals of the same type would be blocked by default. I definitely wish for this behaviour so I decided to test it.

I put a sleep (my own implementation using clock_nanosleep() in a loop which resumes when interrupted) at the end of the signal handler and sent a SIGINT to the child. This duly made it quit and sent SIGCHLD to the parent. I logged the fact and started my sleep for 10 seconds. Now, I sent another SIGINT to the new child (sighandler restarted it first time) and was surprised to see another log and sleep happen.

How can this be? When I attached using a debugger to the parent it clearly showed two different threads interrupted to call my signal handler, both now sat in sleep. If that keeps up I will run out of threads!

I understand putting long sleeps into a signal handler is a daft thing to do but it does illustrate the point; I expected to see the second signal marked as pending in /proc/[PID]/status but instead it's delivered.

Here's the relevant bits of my code:

Set up the SIGCHLD handler:

typedef struct SigActType {
    struct sigaction act;
    int              retval;
    void             (*func)(int);
}SigActType;
static SigActType sigActList[64];

public void setChildHandler(void (*func)(int)) {
    SigActType *sat = &sigActList[SIGCHLD];

    sat->act.sa_sigaction = sigchldHandler;
    sigemptyset(&sat->act.sa_mask);
    sigaddset (&sat->act.sa_mask, SIGTERM);
    sigaddset (&sat->act.sa_mask, SIGINT);
    sigaddset (&sat->act.sa_mask, SIGCHLD);
    sat->act.sa_flags = SA_SIGINFO;
    sat->retval = 0;
    sat->func = func;
    sigaction(SIGCHLD, &sat->act, NULL);
}

static void sigchldHandler(int sig, siginfo_t *si, void *thing) {
    SigActType *sat = &sigActList[SIGCHLD];

    if (sat->func) {
        sat->func(si->si_pid);
    }
}

and using this:

int main(int argc, char **argv) {
    setChildHandler(manageChildSignals);
    ...
}

static void manageChildSignals(int d) {
    if ((pid = waitpid(-1, &stat, WAIT_MYPGRP)) > 0) {
        ... restart child if appropriate
    }
    printf("start of pause...\n");
    mySleep(10);
    printf("end of pause...\n");
}

Stdout clearly shows:

(when I type kill -2 [PID]
start of pause
(when the new child is started and I type kill -2 [NEWPID]
start of pause
...10 seconds slide past...
end of pause
end of pause

I am puzzled as to why this happens. As you can see I even added SIGCHLD to the block mask for sigaction() to try to encourage it to do the right thing.

Any pointers most welcome!

like image 684
jrichemont Avatar asked Dec 19 '25 01:12

jrichemont


1 Answers

signals of the same type would be blocked by default.

Yes, but only for the thread sigaction() is called from.

From man sigaction (bold emphasis by me):

sa_mask specifies a mask of signals which should be blocked (i.e., added to the signal mask of the thread in which the signal handler is invoked) during execution of the signal handler.

As signal dispostion is per process any other thread not blocking the signal in question might receive it, that is get interupted and process it.

If this behaviour is not what you want you should perhaps modify the design of the way your program handles signals in such a way that per default all signals are blocked for each thread, and only one specifiy thread has signal reception unblocked.

Update:

Signals masks are inherited from the parent thread by the child thread.

If signal handling shall be done by one specific thread only, have the main thread block all signals prior to creating any other thread. Then create one specfic thread to do the signal handling, and have this thread unblock the signals to be handled. This concept also allows models like one thread per signal.

In a mutlithreaded environment use pthread_sigmask() to mask signals on a per thread base.

Please note that the behaviour of sigprocmask() in a multithreaded process is unspecified, use pthread_sigmask() then.

like image 160
alk Avatar answered Dec 20 '25 17:12

alk