In a comment to the question, OP states
I am sending the SIGTERM from the parent while the relevant child is at "raise(SIGSTOP)". I think that because the child is in SIGSTOP it doesn't run the signal handler.
Correct. When a process is stopped, it does not receive signals other than SIGCONT
and SIGKILL
(plus SIGSTOP
, SIGTSTP
, SIGTTIN
, SIGTTOU
are ignored). All other signals should become pending, delivered when the process is continued. (Standard POSIX signals are not queued, though, so you can rely on only one standard POSIX signal becoming pending.)
However, I do need to send the SIGTERM only when the child is in SIGSTOP, without sending SIGCONT before.
The target process will receive SIGTERM
only after it is continued. That is how stopped processes behave.
Is there a workaround?
Perhaps; it depends on the requirements. But do note that your intended use case involves behaviour that does not comply with POSIX (i.e., you want a stopped process to react to something other than just being continued or killed outright); and that is the direct reason for the problems you have encountered.
The simplest is to use a variant of SIGCONT
instead of SIGTERM
, to control the terminating of the process; for example, via sigqueue()
, providing a payload identifier that tells the SIGCONT signal handler to treat it as a SIGTERM signal instead (and thus distinguishing between normal SIGCONT signals, and those that are stand-ins for SIGTERM).
A more complicated one is to have the process fork a special monitoring child process, that regularly sends special "check for pending SIGTERM signals" SIGCONT signals, and dies when the parent dies. The child process can be connected to the parent via a pipe (parent having the write end, child the read end), so that when the parent dies, a read()
on the child end returns 0, and the child can exit too. The parent process SIGCONT handler just needs to detect if the signal was sent by the child process — the si_pid
field of the siginfo_t
structure should only match the child process ID if sent by the child —, and if so, check if a SIGTERM is pending, handle it if yes; otherwise just raise SIGSTOP. This approach is very fragile, due to the many possibilities of race windows — especially raising SIGSTOP just after receiving SIGCONT. (Blocking SIGCONT in the signal handler is essential. Also, the monitoring child process should probably be in a separate process group, not attached to any terminal, to avoid being stopped by a SIGSTOP targeted at the entire process group.)
Note that one should only use async-safe functions in signal handlers, and retain errno
unchanged, to keep everything working as expected.
For printing messages to standard error, I often use
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
static int wrerr(const char *msg)
{
const int saved_errno = errno;
const char *end = msg;
ssize_t count;
int retval = 0;
/* Find end of string. strlen() is not async-signal safe. */
if (end)
while (*end)
end++;
while (msg < end) {
count = write(STDERR_FILENO, msg, (size_t)(end - msg));
if (count > 0)
msg += count;
else
if (count != -1) {
retval = EIO;
break;
} else
if (errno != EINTR) {
retval = errno;
break;
}
}
errno = saved_errno;
return retval;
}
which not only is async-signal safe, but also keeps errno
unchanged. It returns 0 if success, and an errno
error code otherwise.
If we expand the prints a bit for clarity, OP's custom signal handler becomes for example
void custom_signal_handler(int signum, siginfo_t* info, void* context) {
if (signum == SIGTERM) {
wrerr("custom_signal_handler(): SIGTERM\n");
} else
if (signum == SIGCONT) {
wrerr("custom_signal_handler(): SIGCONT\n");
}
}
Do note that when this is used, ones program should not use stderr
(from <stdio.h>
) at all, to avoid confusion.