How to stop all child processes spawned by a service

6

2

I have a service running on Ubuntu with the following configuration:

#/etc/init/my_service.conf

start on (local-filesystems and net-device-up IFACE=eth1)

respawn

exec python -u /opt/XYZ/my_prog.py 2>&1 \
                 | logger -t my_prog.py

On stopping the service with sudo service my_service stop, the python process is not killed. Killing the parent process with kill also does not kill the python process.

How do I completely stop the service (i.e all child processes killed)? Ideally, I would like to not modify the configuration file above.

Dhara

Posted 2014-01-28T16:32:49.100

Reputation: 181

Answers

5

Ideally, I would like to not modify the configuration file above.

Tough! It's the right thing to do.

You need to change your exec into script, and stop running that python program in a forked subprocess as part of a pipeline. This ServerFault answer explains how to do this in an embedded shell script. I'd make just one change to the script given there, in the last line:

exec python -u /opt/XYZ/my_prog.py 2>&1

There's no really good reason not to log standard error too, after all.

Ever more complex gyrations to cope with forking, from expect daemon to switching to systemd, miss the point that the right thing to do is to stop the daemon from forking. If there's one good thing to come out of the current kerfuffle, it's the continued confirmation that what IBM wrote and recommended in 1995 has been right all these years.

Get used to the idea of chain loading daemons. There are plenty of toolsets that make such things simple. Get used to the idea of not using shell scripts, too. There are plenty of toolsets that are designed specifically for this work, that eliminate the overheads of shells (which is a known good idea in the Ubuntu world).

For example: The shell commands in the ServerFault answer can be replaced with a script that uses Laurent Bercot's execline tools which are designed to be able to do this very thing without subshells and unlinked FIFOs:

#!/command/execlineb -PW
pipeline -w {
    logger -t my_prog.py
} 
fdmove -c 2 1 
python -u /opt/XYZ/my_prog.py

which you would then simply

exec /foo/this_execlineb_script

With my nosh toolset, it would similarly be a script containing:

#!/usr/local/bin/nosh
pipe 
fdmove -c 2 1 
python -u /opt/XYZ/my_prog.py | logger -t my_prog.py

Or alternatively, one could have this stanza directly in the Upstart job definition (using a trick to avoid shell metacharacters so that Upstart doesn't spawn a shell):

exec /usr/local/bin/exec pipe --separator SPLIT fdmove -c 2 1 python -u /opt/XYZ/my_prog.py SPLIT logger -t my_prog.py

Further reading

JdeBP

Posted 2014-01-28T16:32:49.100

Reputation: 23 855

3

On GNU/Linux, ordinarily, there is no way to stop a service and all child processes it spawned, because child processes can change their PPID (parent process ID). The only way to know would be to trace the system calls to spawn processes as they are made, and keep a list of these processes.

The Ubuntu init system, upstart, does not do this. So the answer to your question is it's impossible -- on Ubuntu -- without:

  1. Modifying that script;
  2. Knowing exactly which process IDs are spawned by that process;
  3. Keeping track of those process IDs manually;
  4. Killing them individually.

This is why you should run a distribution of Linux that runs systemd. As you can see, systemd keeps track of all child processes and can kill every one of them with a single command. This is how GNU/Linux system administration should be, but because systemd is so new, and because it's "Not Invented Here" (meaning, Canonical didn't invent it), Ubuntu doesn't want to use it.

allquixotic

Posted 2014-01-28T16:32:49.100

Reputation: 32 256

1Upstart does send SIGTERM to the entire process group, which by default will include all child processes spawned by the main process. So, while you're correct in that you cannot guarantee this behavior, unless the code explicitly changed the process group ID it will kill any and all children. – Derek – 2016-12-07T22:12:04.773

1

You have skipped the expect stanza. The upstart cookbook specifies:

Warning

This stanza is extremely important: read this section carefully!

Upstart will keep track of the process ID that it thinks belongs to a job. If a job has specified the instance stanza, Upstart will track the PIDs for each unique instance of that job.

If you do not specify the expect stanza, Upstart will track the life cycle of the first PID that it executes in the exec or script stanzas. However, most Unix services will "daemonize", meaning that they will create a new process (using fork(2)) which is a child of the initial process. Often services will "double fork" to ensure they have no association whatsoever with the initial process. (Note that no services will fork more than twice initially since there is no additional benefit in doing so).

In this case, Upstart must have a way to track it, so you can use expect fork, or expect daemon which allows Upstart to use ptrace(2) to "count forks".

To allow Upstart to determine the final process ID for a job, it needs to know how many times that process will call fork(2). Upstart itself cannot know the answer to this question since once a daemon is running, it could then fork a number of "worker" processes which could themselves fork any number of times. Upstart cannot be expected to know which PID is the "master" in this case, considering it does not know if worker processes will be created at all, let alone how many times, or how many times the process will fork initially. As such, it is necessary to tell Upstart which PID is the "master" or parent PID. This is achieved using the expect stanza.

You need this because the pipe | you are using is creating children processes. You can find in the book Advanced Linux programming a brief intro to that, where it is stated that:

For example, this shell command causes the shell to produce two child processes, one for ls and one for less:

 $ ls | less

What I do not know is whether this implies one or two forks, so that I would experiment with modifying the line respawn in your code with either

 expect fork
 respawn

or

 expect daemon
 respawn

I do not believe this can be achieved only with systemd, even though my logo clearly shows I am a fan of systemd.

MariusMatutiae

Posted 2014-01-28T16:32:49.100

Reputation: 41 321

With systemd one wouldn't be employing logger in the first place. Standard output and error can be sent to the journal directly; no subprocesses or pipe in the service definition required. – JdeBP – 2014-01-28T19:14:03.437

@JdeBP I am not arguing with systemd, just saying what the OP asks for can be done even on systems (Ubuntu) where it is not present. – MariusMatutiae – 2014-01-28T19:19:31.423

That's completely backwards, though. What the questioner asks for is how do to something with upstart; and your bringing systemd into it and then saying that it can even be done without systemd is putting the cart entirely before the horse. This isn't something that one would sensibly do with systemd in the first place. So emphasizing that it can even be done without systemd is at best odd. That's not an emphatic aside. Rather, it's a fundamental part of the question. – JdeBP – 2014-01-28T19:57:07.147

@JdeBP J,your comments are always so off the mark,they become tiresome. I mentioned systemd only because allquixotic did. My answer is all about upstart. You brought up systemd once again. If you wish to pick up the usual meaningless fight, go talk to someone else, I am bored already – MariusMatutiae – 2014-01-28T21:32:35.890