Stopping processes and exit statuses

subprocess · Advanced · 10 min

Supervisors, test harnesses and dev servers must stop children cleanly and report why they died.

p.terminate() asks a process to stop: SIGTERM on POSIX, which the program can catch to clean up. p.kill() forces it: SIGKILL, which can't be caught. On Windows both call TerminateProcess. p.send_signal(sig) sends any signal, for example signal.SIGINT to mimic Ctrl+C.

The graceful pattern:

p.terminate()
try:
    p.wait(timeout=5)
except subprocess.TimeoutExpired:
    p.kill()
    p.wait()

Always wait() after signalling, so the process is reaped; an exited child that hasn't been waited for stays a zombie in the process table.

On POSIX, a negative returncode -N means the process was killed by signal N: -15 is SIGTERM, -9 is SIGKILL. A shell reports the same event as 128 + N, so 137 from a shell or Docker means SIGKILL, often the out-of-memory killer. signal.Signals(15).name turns a number into 'SIGTERM'.

CalledProcessError's message also decodes this, saying "died with <Signals.SIGKILL: 9>".

Example

import signal, subprocess, sys

child = """
import signal, sys, time
signal.signal(signal.SIGTERM, lambda *a: (print("child: cleaning up", flush=True), sys.exit(0)))
time.sleep(30)
"""
p = subprocess.Popen([sys.executable, "-c", child])
try:
    p.wait(timeout=0.5)
except subprocess.TimeoutExpired:
    p.terminate()                       # child: cleaning up
    print("graceful exit:", p.wait(timeout=5))      # graceful exit: 0

p = subprocess.Popen(["sleep", "30"])
p.kill()
code = p.wait()
print(code, signal.Signals(-code).name)            # -9 SIGKILL

Remember

  • terminate() = SIGTERM (catchable), kill() = SIGKILL
  • Always wait() after stopping a process to reap it
  • returncode -N means killed by signal N; shells report 128+N

Watch out for

  • Calling kill() first, so the child never gets to flush files or release locks.

Check yourself

  1. A child's returncode is -15 on Linux. What happened?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/subprocess.html#subprocess.Popen.terminate