Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

How can I use SIGUSR1 or SIGUSR2 without risk of terminating the process?

+4
−0

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?

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

1 answer

+5
−0

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.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

1 comment thread

Reading SigCgt (3 comments)

Sign up to answer this question »