ZetCode

Python os.waitpid Function

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.

Basic Definitions

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.

Basic Process Waiting

This example demonstrates the simplest use of os.waitpid to wait for a child process to complete. The parent process waits indefinitely.

basic_wait.py
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.

Non-blocking Wait with WNOHANG

The WNOHANG option makes os.waitpid non-blocking, returning immediately if the child hasn't exited yet. This allows polling.

non_blocking.py
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.

Waiting for Any Child 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.

wait_any.py
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.

Handling Terminated Children

This example shows how to handle children terminated by signals using the status information returned by os.waitpid.

signal_handling.py
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.

Waiting for Process Group

Using a pid less than -1 waits for any child in the specified process group. This is useful for managing groups of related processes.

process_group.py
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.

Advanced Status Interpretation

This example demonstrates comprehensive status interpretation using all the available macros for examining the waitpid status value.

status_interpretation.py
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.

Waiting for Orphaned Processes

This example shows how os.waitpid handles orphaned processes that have been reparented to init (PID 1) when the original parent exits.

orphaned.py
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.

Security Considerations

Best Practices

Source References

Author

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.