A timeout on the direct child does not end its descendants; a new POSIX session gives the parent a group to signal.
Python POSIX subprocess timeout: stop the owned process group
Operation contract
The parent starts a fixed child in a new session. The child sleeps, so a short wait expires. The parent sends SIGKILL to that owned group and then reaps the direct child. This local fixture has no grandchildren; it checks session setup and the direct-child cleanup path.
Failure boundary
This is POSIX-specific and does not prove cleanup of an arbitrary process tree. Children can leave the process group, ignore softer signals, or race with shutdown; production supervisors need a policy for each case. Never send a group signal using an unverified PID, and never use the parent's process group as the target. Windows uses different job and process-control APIs.
Working program
import os
import signal
import subprocess
import sys
child = subprocess.Popen(
[sys.executable, "-c", "import time; time.sleep(47)"],
start_new_session=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
child.wait(timeout=0.03)
except subprocess.TimeoutExpired:
os.killpg(child.pid, signal.SIGKILL)
child.wait(timeout=5)
print("child_reaped", child.returncode is not None)Output
child_reaped TrueCosts and limits
The timeout adds bounded waiting and a process launch; group signaling is constant-time from this program's perspective. OS scheduling and teardown dominate this tiny fixture.
Common Mistakes
- A timed-out wait leaves a child running until explicitly stopped.
- A direct-child kill does not necessarily stop grandchildren.
- Process-group IDs must belong to a session created for this job.
