I've been studying process management using shell scripts and I'm starting to realise how difficult it is to make sure that it's done right.
For example, you can record the PID of a program to a file, wait
on it, and clean up the PID file after the program exits.
If you were to try and kill
this daemon from an init script, for example, you might think of doing something like this:
do_stop() {
kill $(</var/run/program.pid)
}
This obviously doesn't work. Between obtaining the PID and sending the kill signal, another process could have died and taken its place.
The correct way seems to require using IPC in the parent of the program to send a kill signal to its child. This will ensure that the PID of the process hasn't been reused by another.
I've been trying to write my own init scripts that are as correct as possible. In this case, I've been writing one for NRPE. NRPE unfortunately daemonizes and disowns itself to init, which means I can't wait
on it. Instead, I came up with the following solution:
do_stop() {
echo "Stopping (sending SIGTERM to) nrpe"
pkill -u nrpe || { echo >&2 "nrpe isn't running"; exit 1; }
}
The only process that the nrpe
user runs is NRPE itself, and considering the system is under my control I consider this a relatively sane solution.
What I'm curious about is the atomicity of pkill
(if that's the right word to use). I assume pkill
follows these steps:
- Looks up the PID in the process table after parsing the arguments for the process criteria.
- Sends
SIGTERM
(by default) to the obtained PID
Let's say pkill -u nrpe
gives a PID of 42 in step 1. Is it possible that nrpe
's process could die and another one could spawn in its place before step 2 occurs?
systemd
orupstart
for example. Even within SysV style init scripts there are often "facilities" available to you to do most of the heavy lifting so there's typically no need to do these things yourself, unless you're just learning about it.