ZetCode

Python os.wait Function

Last modified April 11, 2025

This comprehensive guide explores Python's os.wait function, which waits for child process completion. We'll cover process management, exit status interpretation, and practical synchronization examples.

Basic Definitions

The os.wait function suspends execution until a child process terminates. It returns a tuple containing the process ID and exit status.

Key features: blocks until child process exits, reaps zombie processes, returns (pid, status) tuple. Only works with child processes of the caller.

Basic Process Waiting

This example demonstrates the simplest use of os.wait to wait for a single child process to complete. The parent forks a child that sleeps.

basic_wait.py
import os
import time

pid = os.fork()

if pid == 0:  # Child process
    print("Child process started")
    time.sleep(2)
    print("Child process exiting")
    os._exit(0)
else:  # Parent process
    print(f"Parent waiting for child {pid}")
    child_pid, status = os.wait()
    print(f"Child {child_pid} exited with status {status}")

The parent process forks a child that sleeps for 2 seconds. The parent calls os.wait() which blocks until the child terminates. The exit status is printed.

Note that os._exit() is used in the child to ensure proper process termination without running Python's cleanup handlers.

Handling Multiple Child Processes

This example shows how to wait for multiple child processes using a loop. The parent creates several children and waits for each to complete.

multiple_children.py
import os
import time
import random

children = []

# Create 3 child processes
for i in range(3):
    pid = os.fork()
    if pid == 0:  # Child
        sleep_time = random.randint(1, 3)
        print(f"Child {os.getpid()} sleeping for {sleep_time}s")
        time.sleep(sleep_time)
        os._exit(0)
    else:  # Parent
        children.append(pid)

# Wait for all children to complete
while children:
    pid, status = os.wait()
    print(f"Child {pid} exited with status {status}")
    children.remove(pid)

print("All children have exited")

The parent creates 3 child processes with random sleep durations. It maintains a list of child PIDs and waits for each one in sequence using os.wait().

The order of completion depends on the sleep times. os.wait() returns for any terminated child, not necessarily in creation order.

Non-blocking Wait with WNOHANG

The WNOHANG option makes os.waitpid non-blocking, allowing polling for child process completion. This example demonstrates periodic status checking.

non_blocking.py
import os
import time

pid = os.fork()

if pid == 0:  # Child
    print("Child running")
    time.sleep(3)
    print("Child exiting")
    os._exit(42)
else:  # Parent
    print(f"Parent waiting for child {pid}")
    while True:
        try:
            child_pid, status = os.waitpid(pid, os.WNOHANG)
            if child_pid == 0:
                print("Child still running...")
                time.sleep(1)
            else:
                print(f"Child exited with status {status}")
                break
        except ChildProcessError:
            print("No such child process")
            break

The parent periodically checks the child's status using os.waitpid with WNOHANG. If the child hasn't exited, waitpid returns (0, 0) immediately.

This pattern is useful when the parent needs to perform other work while monitoring child processes without blocking.

Interpreting Exit Status

The exit status returned by os.wait contains encoded information. This example shows how to interpret it using os.WIFEXITED and related functions.

exit_status.py
import os
import sys

pid = os.fork()

if pid == 0:  # Child
    print("Child running")
    # Exit with different statuses
    if len(sys.argv) > 1:
        os._exit(int(sys.argv[1]))
    else:
        os._exit(0)
else:  # Parent
    pid, status = os.wait()
    print(f"Raw status: {status}")
    
    if os.WIFEXITED(status):
        print(f"Child exited normally with status {os.WEXITSTATUS(status)}")
    elif os.WIFSIGNALED(status):
        print(f"Child killed by signal {os.WTERMSIG(status)}")
    elif os.WIFSTOPPED(status):
        print(f"Child stopped by signal {os.WSTOPSIG(status)}")

The child exits with either a status provided as argument or 0. The parent interprets the status using os.WIF* functions to determine how the child exited.

These macros help distinguish normal exits from terminations by signals or process stops, providing detailed process termination information.

Waiting for Specific Process

os.waitpid allows waiting for a specific child process rather than any child. This example demonstrates targeted process waiting.

specific_process.py
import os
import time

# Create two children
child1 = os.fork()
if child1 == 0:
    time.sleep(1)
    os._exit(10)

child2 = os.fork()
if child2 == 0:
    time.sleep(2)
    os._exit(20)

# Parent waits for specific child
print(f"Waiting for child {child2}")
pid, status = os.waitpid(child2, 0)
print(f"Child {pid} exited with status {status}")

# Now wait for remaining child
pid, status = os.wait()
print(f"Child {pid} exited with status {status}")

The parent creates two children with different sleep durations. It first waits specifically for child2 using waitpid, then waits for any remaining child.

This approach is useful when you need to manage multiple children with different priorities or dependencies between them.

Handling Orphaned Processes

This example shows how to handle cases where child processes might become orphaned. The parent process terminates before waiting for its children.

orphaned_process.py
import os
import time
import sys

pid = os.fork()

if pid == 0:  # Child
    print("Child running")
    time.sleep(5)
    print("Child exiting")
    os._exit(0)
else:  # Parent
    print(f"Parent created child {pid}")
    if len(sys.argv) > 1 and sys.argv[1] == "wait":
        pid, status = os.wait()
        print(f"Parent saw child exit with status {status}")
    else:
        print("Parent exiting without waiting")
        sys.exit(0)

When run without arguments, the parent exits immediately, orphaning the child. With "wait" argument, the parent waits properly. Observe process hierarchy.

Orphaned children are adopted by init (PID 1) which will eventually reap them. Proper process management prevents zombie processes.

Error Handling

This example demonstrates proper error handling when using os.wait, including cases with no child processes or interrupted system calls.

error_handling.py
import os
import time
import errno

# Case 1: No child processes
try:
    pid, status = os.wait()
    print(f"Child {pid} exited with status {status}")
except ChildProcessError:
    print("No child processes to wait for")

# Case 2: Interrupted system call
pid = os.fork()
if pid == 0:
    time.sleep(3)
    os._exit(0)
else:
    try:
        # Simulate interrupt (in real code this might happen naturally)
        import signal
        signal.alarm(1)  # SIGALRM in 1 second
        pid, status = os.wait()
    except InterruptedError:
        print("Wait was interrupted")
        # Typically you would retry the wait here
        pid, status = os.wait()
        print(f"Child {pid} exited with status {status}")

The first case shows handling when no children exist. The second demonstrates interruption handling, common with signal handlers. Proper error recovery.

Always handle potential errors when working with process management to avoid unexpected behavior or resource leaks in your application.

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.