SET_PDEATHSIG From Python
April 01, 2021
You've got a python process that runs another process and waits for it to finish. Maybe it's some ffmpeg process or something. Everything is great until, one day, you notice that you've got dangling ffmpeg processes.
Reparenting to init
Why does this happen? Your process tree looks something like
init → [stuff] → [more stuff] → python → ffmpeg
where python is the parent of ffmpeg. It's a moderately common misconception that when a parent process dies, its entire child process tree is terminated too. In fact, if python dies, ffmpeg is simply reparented to init:
init → ffmpeg
To see this in action,
import subprocess, sys
subprocess.Popen(['sleep', '3'])
sys.exit()
Then, ps x | grep sleep
.
SET_PDEATHSIG
Enter SET_PDEATHSIG
.
This tells the kernel to send you a signal when your parent dies.
To call it, we just
#include <stdio.h>
#include <sys/prctl.h>
if (prctl(PR_SET_PDEATHSIG, SIGKILL))
perror("SET_PDEATHSIG");
Of course, we have python code, so we use ctypes:
import ctypes, ctypes.util, signal
# https://github.com/torvalds/linux/blob/v5.11/include/uapi/linux/prctl.h#L9
PR_SET_PDEATHSIG = 1
def set_pdeathsig():
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
if libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL) != 0:
raise OSError(ctypes.get_errno(), 'SET_PDEATHSIG')
But wait, how does this help us?
Setting the parent death signal of the python process makes it so we get
SIGKILL
-ed when our parent dies, but the point of this was to kill ffmpeg when python dies.
At first glance, it looks like we need to call prctl
from inside the ffmpeg code.
But it turns out that PDEATHSIG is inherited across exec
s,
so we simply need to prctl
after fork
ing but before exec
ing ffmpeg.
Conveniently, subprocess
has a
preexec_fn
for doing precisely this!
import ctypes
import ctypes.util
import signal
import subprocess
import sys
# https://github.com/torvalds/linux/blob/v5.11/include/uapi/linux/prctl.h#L9
PR_SET_PDEATHSIG = 1
def set_pdeathsig():
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
if libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL) != 0:
raise OSError(ctypes.get_errno(), 'SET_PDEATHSIG')
subprocess.Popen(['sleep', '3'], preexec_fn=set_pdeathsig)
sys.exit()