36

I am running on a linux machine a python script which creates a child process using subprocess.check_output() as it follows:

subprocess.check_output(["ls", "-l"], stderr=subprocess.STDOUT)

The problem is that even if the parent process dies, the child is still running. Is there any way I can kill the child process as well when the parent dies?

jfs
  • 399,953
  • 195
  • 994
  • 1,670
Clara
  • 2,935
  • 6
  • 34
  • 49
  • [This](http://stackoverflow.com/a/14128476/2235132) answer might help. – devnull Oct 18 '13 at 10:42
  • It doesn't help me when using the check_output fct, because I don't get the pid of the child process, hence I can't kill it... – Clara Oct 18 '13 at 10:43
  • 1
    related: [How to make child process die after parent exits?](http://stackoverflow.com/q/284325/4279) and [Best way to kill all child processes](http://stackoverflow.com/q/392022/4279) – jfs Oct 24 '13 at 16:01

5 Answers5

31

Yes, you can achieve this by two methods. Both of them require you to use Popen instead of check_output. The first is a simpler method, using try..finally, as follows:

from contextlib import contextmanager

@contextmanager
def run_and_terminate_process(*args, **kwargs):
try:
    p = subprocess.Popen(*args, **kwargs)
    yield p        
finally:
    p.terminate() # send sigterm, or ...
    p.kill()      # send sigkill

def main():
    with run_and_terminate_process(args) as running_proc:
        # Your code here, such as running_proc.stdout.readline()

This will catch sigint (keyboard interrupt) and sigterm, but not sigkill (if you kill your script with -9).

The other method is a bit more complex, and uses ctypes' prctl PR_SET_PDEATHSIG. The system will send a signal to the child once the parent exits for any reason (even sigkill).

import signal
import ctypes
libc = ctypes.CDLL("libc.so.6")
def set_pdeathsig(sig = signal.SIGTERM):
    def callable():
        return libc.prctl(1, sig)
    return callable
p = subprocess.Popen(args, preexec_fn = set_pdeathsig(signal.SIGTERM))
Nicholas Franceschina
  • 6,009
  • 6
  • 36
  • 51
micromoses
  • 6,747
  • 2
  • 20
  • 29
  • If you need the output of the command, you can use `p.stdout.read()` or `p.stdout.readlines()` – micromoses Oct 18 '13 at 11:07
  • 2
    the 1st method just starts and *immediately* kills the subprocess, you should add `p.communicate()/p.wait()` after `Popen()` call. [The 2nd method works only on Linux](http://www.evans.io/posts/killing-child-processes-on-parent-exit-prctl/) – jfs Oct 24 '13 at 15:52
  • @J.F., You are right, it was not clear from my example that the program's code should come right before the `finally` statement, with proper indentation to the `try`.. I think it is clearer now. Anyway, it is not clear from the question how the parent may die before the child (unless killed), so I can't assume the actual command is `ls`, or that the child should be waited for (could be some sort of server?). As for your second comment, the question states that the system is linux. – micromoses Oct 24 '13 at 18:19
31

Your problem is with using subprocess.check_output - you are correct, you can't get the child PID using that interface. Use Popen instead:

proc = subprocess.Popen(["ls", "-l"], stdout=PIPE, stderr=PIPE)

# Here you can get the PID
global child_pid
child_pid = proc.pid

# Now we can wait for the child to complete
(output, error) = proc.communicate()

if error:
    print "error:", error

print "output:", output

To make sure you kill the child on exit:

import os
import signal
def kill_child():
    if child_pid is None:
        pass
    else:
        os.kill(child_pid, signal.SIGTERM)

import atexit
atexit.register(kill_child)
Miebster
  • 2,365
  • 4
  • 21
  • 27
cdarke
  • 42,728
  • 8
  • 80
  • 84
  • OK, I didn't realize that communicate() actually waits for the child process to finish. Just needed a fct that returns the output and error and waits for the subprocess to finish, plus kills the child when the parent dies. – Clara Oct 18 '13 at 12:04
  • 2
    If the parent crashes then there is no guarantee that `atexit` handlers will be called. btw, if you have `proc` object; you could call `proc.kill()` directly (no need to extract the pid first) – jfs Oct 24 '13 at 15:58
  • 1
    @J.F. Sebastian: If the parent crashes then ANY mechanism might not get called, unless it is done by a third party (and then that might crash). Must admit that I forgot about `proc.kill()` - good point. – cdarke Oct 26 '13 at 20:35
  • 1
    @cdarke: see [`prctl()`-based solution](http://stackoverflow.com/a/19448096/4279) (the third party is the kernel). – jfs Oct 27 '13 at 06:28
  • 1
    Can use it as a decorator now (it's going to look weird cause there's no formatting in comments): @atexit.register def kill_child(): .. – demented hedgehog Jan 29 '16 at 02:31
2

Don't know the specifics, but the best way is still to catch errors (and perhaps even all errors) with signal and terminate any remaining processes there.

import signal
import sys
import subprocess
import os

def signal_handler(signal, frame):
    sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)

a = subprocess.check_output(["ls", "-l"], stderr=subprocess.STDOUT)

while 1:
    pass # Press Ctrl-C (breaks the application and is catched by signal_handler()

This is just a mockup, you'd need to catch more than just SIGINT but the idea might get you started and you'd need to check for spawned process somehow still.

http://docs.python.org/2/library/os.html#os.kill http://docs.python.org/2/library/subprocess.html#subprocess.Popen.pid http://docs.python.org/2/library/subprocess.html#subprocess.Popen.kill

I'd recommend rewriting a personalized version of check_output cause as i just realized check_output is really just for simple debugging etc since you can't interact so much with it during executing..

Rewrite check_output:

from subprocess import Popen, PIPE, STDOUT
from time import sleep, time

def checkOutput(cmd):
    a = Popen('ls -l', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
    print(a.pid)
    start = time()
    while a.poll() == None or time()-start <= 30: #30 sec grace period
        sleep(0.25)
    if a.poll() == None:
        print('Still running, killing')
        a.kill()
    else:
        print('exit code:',a.poll())
    output = a.stdout.read()
    a.stdout.close()
    a.stdin.close()
    return output

And do whatever you'd like with it, perhaps store the active executions in a temporary variable and kill them upon exit with signal or other means of intecepting errors/shutdowns of the main loop.

In the end, you still need to catch terminations in the main application in order to safely kill any childs, the best way to approach this is with try & except or signal.

Torxed
  • 22,866
  • 14
  • 82
  • 131
0

As of Python 3.2 there is a ridiculously simple way to do this:

from subprocess import Popen

with Popen(["sleep", "60"]) as process:
    print(f"Just launched server with PID {process.pid}")

I think this will be best for most use cases because it's simple and portable, and it avoids any dependence on global state.

If this solution isn't powerful enough, then I would recommend checking out the other answers and discussion on this question or on Python: how to kill child process(es) when parent dies?, as there are a lot of neat ways to approach the problem that provide different trade-offs around portability, resilience, and simplicity.

paulkernfeld
  • 2,171
  • 1
  • 16
  • 16
-2

Manually you could do this:

ps aux | grep <process name>

get the PID(second column) and

kill -9 <PID> -9 is to force killing it

Ciprian Tarta
  • 484
  • 5
  • 12
  • I was hoping to be able to do it programatically, either by getting the pid of the child process, or by other tricks. Plus I can't even kill the child manually, because I don't have its pid. – Clara Oct 18 '13 at 10:50
  • It's the same link as the one in the comment above - with Popen you get back the pid of the child process, while I don't have that when I use check_output. – Clara Oct 18 '13 at 10:55
  • 1
    @Clara You can get the output using `Popen` too, `subprocess.Popen(...).communicate()[0]`. – Ashwini Chaudhary Oct 18 '13 at 11:06