How can I use SIGUSR1 or SIGUSR2 without risk of terminating the process?
SIGUSR1 and SIGUSR2 are user-defined signals. Imagine there is a tool designed to do something useful upon receiving one or the other.
The problem is the default action for these signals is Term
(see man 7 signal
), so it's safe to send SIGUSR1 or SIGUSR2 to the tool only after it started handling (or ignoring, or blocking) the signal. Send it too soon and the process will be terminated.
Personally I'm surprised by this default behavior. When I want to use SIGUSR1 or SIGUSR2, it's because there is (or I hope there is) a handler that does something useful. In no case I want the signal to terminate the process. If I wanted to terminate the process, I would simply generate SIGTERM.
Our example tool can be GNU dd
. If it manages to set things up then it will react to SIGUSR1 by printing I/O statistics to its stderr. But try this:
dd if=/dev/zero of=/dev/null & kill -s USR1 "$!"; sleep 1; kill -s USR1 "$!"; sleep 1; kill "$!"
Most likely you will see dd
terminated by SIGUSR1 generated by the first kill
. The second and the third kill
will tell you there is no such process
.
If you sleep 1
before the first kill
then you most likely give dd
enough time to set things up and each kill
will work as intended:
dd if=/dev/zero of=/dev/null & sleep 1; kill -s USR1 "$!"; sleep 1; kill -s USR1 "$!"; sleep 1; kill "$!"
But this is not a firm method, as there is no guarantee sleep 1
is enough. In theory no interval is long enough.
How can I use SIGUSR1 or SIGUSR2 without any risk of terminating the process?
1 answer
If you are about to start the tool
Start the tool from a process that already blocks or ignores SIGUSR1 and/or SIGUSR2 and let the tool inherit these settings.
A blocked signal, when generated, will not be delivered to the process until the process unblocks it. An ignored signal, when delivered, will cause no action.
After registering a handler for a signal, a sane tool should unignore and unblock the signal. Blocking SIGUSR1 until the tool is ready to handle it is a good solution to the problem in question. It's crucial to start blocking (or ignoring) in the parent process, so the child is immune to the signal from its very birth and there is no time window when SIGUSR1 can terminate it.
The example with GNU dd
uses shell code to start dd
and kill
. Shells I know provide no way to block a signal; with trap
they can ignore a signal. The snippets below use the flawed code from the question, plus a prior trap
.
The default behavior (like in the question; the explicit trap
changes nothing*):
trap - USR1 # default handling, SIGUSR1 causes Term
dd if=/dev/zero of=/dev/null & kill -s USR1 "$!"; sleep 1; kill -s USR1 "$!"; sleep 1; kill "$!"
The fix:
trap '' USR1 # ignoring SIGUSR1
dd if=/dev/zero of=/dev/null & kill -s USR1 "$!"; sleep 1; kill -s USR1 "$!"; sleep 1; kill "$!"
In the fixed code the first kill
will most likely generate SIGUSR1 that would otherwise terminate dd
, but here it will be ignored. The second kill
will most likely generate SIGUSR1 late enough, so it will be handled. The third kill
will kill dd
as intended.
If we managed to block the signal then the first SIGUSR1 would be handled (as soon as possible) rather than ignored; this would be even better. I cannot do this in a shell though. If you run a program (as opposed to shell code) to start your tool and send SIGUSR1 or SIGUSR2 to it, then probably you have means to block the signal in the parent process.
One way or another (i.e. with ignoring or blocking the signal), this is how you can send SIGUSR1 or SIGUSR2 to your tool without any risk of terminating it.
* Strictly trap - USR1
resets the signal handling to what it was when the shell started. This is not necessarily the default described in man 7 signal
, as some ancestor of the shell may have interfered. I guess for SIGUSR1 such interference is quite unlikely, unless the shell or one of its ancestors is also a tool the question is about and our fix has been applied to it.
If the tool is already running
If the tool is already running, or if you cannot change the way it's started, you can delay sending SIGUSR1 or SIGUSR2 until you're sure the signal will be caught. You can tell this by parsing the output of grep ^SigCgt: /proc/PID/status
, the information is "encoded" there. I'm not a programmer and I don't know if there are functions that allow you to easily retrieve the information in some plain form, I won't elaborate.
In general such approach is prone to TOCTOU, but if the tool is sane enough not to get back to the default Term
after registering a handler, then once you notice there is a handler, you're safe.
0 comment threads