Last modified April 11, 2025
This comprehensive guide explores Python's os.waitpid function,
which waits for a child process to complete and retrieves its exit status.
We'll cover process management, status interpretation, and practical examples.
The os.waitpid function waits for a child process specified by
process ID (pid) to complete and returns its exit status information.
Key parameters: pid (process ID to wait for), options (flags like WNOHANG). Returns a tuple of (pid, status). Status contains exit code and signal info.
This example demonstrates the simplest use of os.waitpid to wait
for a child process to complete. The parent process waits indefinitely.
import os
import sys
# Fork a child process
pid = os.fork()
if pid == 0:
# Child process
print("Child process running")
sys.exit(42)
else:
# Parent process
print(f"Parent waiting for child {pid}")
child_pid, status = os.waitpid(pid, 0)
print(f"Child {child_pid} exited with status {status}")
if os.WIFEXITED(status):
print(f"Exit code: {os.WEXITSTATUS(status)}")
The parent process forks a child that exits with code 42. The parent waits for the child to complete using waitpid. WIFEXITED checks for normal exit.
The status contains more information than just the exit code, including signal information if the process was terminated by a signal.
The WNOHANG option makes os.waitpid non-blocking, returning
immediately if the child hasn't exited yet. This allows polling.
import os
import time
pid = os.fork()
if pid == 0:
# Child process runs for 3 seconds
time.sleep(3)
os._exit(0)
# Parent polls child status
while True:
child_pid, status = os.waitpid(pid, os.WNOHANG)
if child_pid == 0:
print("Child still running...")
time.sleep(1)
else:
print(f"Child {child_pid} exited")
break
The parent checks the child status every second without blocking. When the child exits after 3 seconds, waitpid returns the child's PID and status.
This pattern is useful when you need to monitor a process while continuing to perform other tasks in the parent process.
Using pid of -1 waits for any child process. This is useful when managing multiple children and you don't know which will complete first.
import os
import time
import random
# Create 3 child processes
children = []
for i in range(3):
pid = os.fork()
if pid == 0:
# Child sleeps for random time and exits
sleep_time = random.randint(1, 5)
time.sleep(sleep_time)
os._exit(sleep_time)
else:
children.append(pid)
# Wait for any child to complete
while children:
pid, status = os.waitpid(-1, 0)
if os.WIFEXITED(status):
print(f"Child {pid} exited after {os.WEXITSTATUS(status)} seconds")
children.remove(pid)
print("All children exited")
The parent creates 3 children that sleep for random durations. Using pid=-1, it waits for whichever child exits first, removing it from the tracking list.
This approach is common in servers that handle multiple client processes concurrently.
This example shows how to handle children terminated by signals using
the status information returned by os.waitpid.
import os
import signal
import time
pid = os.fork()
if pid == 0:
# Child process waits to be killed
print("Child running (PID: {})".format(os.getpid()))
while True:
time.sleep(1)
else:
# Parent waits briefly then kills child
time.sleep(2)
os.kill(pid, signal.SIGTERM)
# Wait for child and check status
child_pid, status = os.waitpid(pid, 0)
if os.WIFSIGNALED(status):
sig = os.WTERMSIG(status)
print(f"Child {child_pid} killed by signal {sig} ({signal.Signals(sig).name})")
elif os.WIFEXITED(status):
print(f"Child exited normally with status {os.WEXITSTATUS(status)}")
The parent sends SIGTERM to the child after 2 seconds. waitpid returns status information that we decode using WIFSIGNALED and WTERMSIG.
This demonstrates how to distinguish between normal exits and signal-induced terminations when managing child processes.
Using a pid less than -1 waits for any child in the specified process group. This is useful for managing groups of related processes.
import os
import time
# Create a new process group
os.setpgrp()
# Create 3 child processes in the same group
for i in range(3):
pid = os.fork()
if pid == 0:
# Children sleep and exit
time.sleep(i + 1)
os._exit(0)
# Parent waits for any child in our process group
# Negative PID means wait for any in that process group
group_pid = -os.getpgrp()
exited = 0
while exited < 3: child_pid, status = os.waitpid(group_pid, 0) exited += 1 print(f"Child {child_pid} exited (total exited: {exited})")
The parent creates a new process group and spawns 3 children. Using a negative pid equal to the negative of the group ID, it waits for any child in the group.
This technique is particularly useful when you want to manage all descendants of a process together.
This example demonstrates comprehensive status interpretation using all the available macros for examining the waitpid status value.
import os
import signal
import random
def analyze_status(pid, status):
print(f"\nAnalysis for process {pid}:")
if os.WIFEXITED(status):
print(f" Exited normally with status {os.WEXITSTATUS(status)}")
if os.WIFSIGNALED(status):
print(f" Killed by signal {os.WTERMSIG(status)}")
print(f" Core dumped: {os.WCOREDUMP(status)}")
if os.WIFSTOPPED(status):
print(f" Stopped by signal {os.WSTOPSIG(status)}")
if os.WIFCONTINUED(status):
print(" Continued")
# Create child that might exit normally, be killed, or stopped
pid = os.fork()
if pid == 0:
# Child process
fate = random.choice(['exit', 'segfault', 'stop'])
if fate == 'exit':
os._exit(42)
elif fate == 'segfault':
os.kill(os.getpid(), signal.SIGSEGV)
else:
os.kill(os.getpid(), signal.SIGSTOP)
os._exit(0)
else:
# Parent waits and analyzes
child_pid, status = os.waitpid(pid, 0)
analyze_status(child_pid, status)
The child randomly chooses to exit normally, segfault, or stop. The parent uses various macros to interpret the status returned by waitpid.
This demonstrates the full range of status interpretation possibilities when working with child processes in Python.
This example shows how os.waitpid handles orphaned processes
that have been reparented to init (PID 1) when the original parent exits.
import os
import time
import sys
pid = os.fork()
if pid == 0:
# Child becomes orphan
print(f"Child PID: {os.getpid()}")
print("Child sleeping for 10 seconds (will be orphaned)")
time.sleep(10)
print("Orphan child exiting")
sys.exit(0)
else:
# Parent exits immediately
print(f"Parent exiting, child will be orphaned")
sys.exit(0)
# This code only runs in the original parent
# The orphaned child continues running but can't be waited for
# by this process as it has been reparented to init
The parent exits immediately, orphaning the child which continues running. The original parent can no longer wait for this child as it's been reparented.
This demonstrates an important limitation - you can only wait for direct child processes, not grandchildren or orphaned processes.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Python tutorials.