Python os.wait3 Function
Last modified April 11, 2025
This comprehensive guide explores Python's os.wait3 function,
which waits for child process completion and returns resource usage data.
We'll cover process management, status interpretation, and practical examples.
Basic Definitions
The os.wait3 function waits for any child process to complete
and returns its process ID, exit status, and resource usage information.
Key parameters: options (WNOHANG, WUNTRACED). Returns a tuple of (pid, status, resource_usage). Available on Unix-like systems only.
Basic Process Waiting
The simplest use of os.wait3 waits for any child process to
terminate. This example creates a child process and waits for its completion.
import os
import sys
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}")
pid, status, rusage = os.wait3(0)
print(f"Child {pid} exited with status {status}")
print(f"Exit code: {os.WEXITSTATUS(status)}")
print(f"Resource usage: {rusage}")
This example forks a child process that exits with code 42. The parent waits for the child and prints its exit status and resource usage information.
The os.WEXITSTATUS macro extracts the exit code from the status
value returned by wait3. Resource usage includes CPU time and memory stats.
Non-blocking Wait with WNOHANG
The WNOHANG option makes os.wait3 non-blocking. It returns
immediately if no child process has terminated. This allows polling.
import os
import time
pid = os.fork()
if pid == 0: # Child
print("Child sleeping for 2 seconds")
time.sleep(2)
os._exit(0)
else: # Parent
print("Parent polling for child completion")
while True:
result = os.wait3(os.WNOHANG)
if result[0] != 0: # Child exited
print(f"Child {result[0]} exited")
break
print("Child still running...")
time.sleep(0.5)
The parent process polls for child completion every 0.5 seconds using WNOHANG. This prevents blocking while waiting for the child to finish sleeping.
When no child has exited, wait3 returns (0, 0, 0). The actual child PID is only returned when the child terminates.
Handling Multiple Child Processes
os.wait3 can manage multiple child processes. This example creates
several children and waits for them in sequence as they terminate.
import os
import time
import random
children = []
for i in range(3):
pid = os.fork()
if pid == 0: # Child
sleep_time = random.randint(1, 3)
print(f"Child {i} sleeping for {sleep_time}s")
time.sleep(sleep_time)
os._exit(i)
else: # Parent
children.append(pid)
print(f"Parent waiting for children {children}")
while children:
pid, status, _ = os.wait3(0)
exit_code = os.WEXITSTATUS(status)
print(f"Child {pid} exited with code {exit_code}")
children.remove(pid)
Three child processes are created with random sleep durations. The parent waits for each child in the order they complete, not the order they were created.
The children list tracks active child PIDs. As each child exits, it's removed from the list until all children have been reaped.
Capturing Resource Usage
The resource usage tuple from os.wait3 provides detailed
information about CPU time, memory usage, and other system resources.
import os
import time
def child_work():
# Simulate CPU and memory usage
start = time.time()
data = []
while time.time() - start < 1: # Run for 1 second
data.append("x" * 1000) # Allocate memory
return len(data)
pid = os.fork()
if pid == 0: # Child
count = child_work()
os._exit(count)
else: # Parent
pid, status, rusage = os.wait3(0)
print(f"Child user time: {rusage.ru_utime:.3f}s")
print(f"Child system time: {rusage.ru_stime:.3f}s")
print(f"Max RSS: {rusage.ru_maxrss} KB")
print(f"Page faults: {rusage.ru_majflt}")
print(f"Blocks in: {rusage.ru_inblock}")
print(f"Blocks out: {rusage.ru_oublock}")
The child process performs CPU-intensive work and memory allocation. The parent captures detailed resource usage statistics after the child exits.
Key metrics include user CPU time, system CPU time, maximum resident set size, page faults, and block I/O operations.
Handling Stopped Processes
Using WUNTRACED option, os.wait3 can detect when child processes
are stopped (e.g., by SIGSTOP) rather than terminated.
import os
import signal
import time
pid = os.fork()
if pid == 0: # Child
print("Child running, will be stopped")
time.sleep(10) # Give time to send signal
os._exit(0)
else: # Parent
print(f"Parent waiting for child {pid}")
time.sleep(1) # Wait a moment before stopping
os.kill(pid, signal.SIGSTOP)
pid, status, _ = os.wait3(os.WUNTRACED)
if os.WIFSTOPPED(status):
print(f"Child {pid} stopped by signal {os.WSTOPSIG(status)}")
# Continue the child
os.kill(pid, signal.SIGCONT)
pid, status, _ = os.wait3(0)
print(f"Child {pid} exited with status {status}")
The parent sends SIGSTOP to the child, then detects the stopped state using WUNTRACED. It then continues the child with SIGCONT and waits for termination.
os.WIFSTOPPED checks if the child was stopped, and
os.WSTOPSIG gets the stopping signal number.
Error Handling
os.wait3 can raise OSError in certain conditions. This example
demonstrates proper error handling when waiting for child processes.
import os
import errno
try:
# Try to wait when no children exist
pid, status, rusage = os.wait3(os.WNOHANG)
print("This won't be reached")
except OSError as e:
if e.errno == errno.ECHILD:
print("No child processes to wait for")
else:
print(f"Unexpected error: {e}")
# Create and immediately wait for child
pid = os.fork()
if pid == 0:
os._exit(0)
else:
try:
pid, status, rusage = os.wait3(0)
print(f"Child {pid} exited normally")
except OSError as e:
print(f"Error waiting for child: {e}")
The first attempt to wait fails with ECHILD (no child processes). The second part shows successful waiting after creating a child process.
Proper error handling is essential as system conditions may change between process creation and waiting operations.
Advanced Process Groups
os.wait3 can monitor processes in specific process groups.
This example demonstrates managing a process group of children.
import os
import time
# Create new process group
os.setpgrp()
children = []
for i in range(3):
pid = os.fork()
if pid == 0: # Child
# Children in same process group
print(f"Child {i} in group {os.getpgrp()}")
time.sleep(i + 1)
os._exit(0)
else:
children.append(pid)
print(f"Parent waiting for children in group {os.getpgrp()}")
while children:
try:
pid, status, _ = os.wait3(0)
if pid > 0:
print(f"Child {pid} exited")
children.remove(pid)
except OSError as e:
if e.errno == errno.ECHILD:
break
raise
print("All children exited")
This creates a new process group and several child processes. The parent waits for all children in its process group to exit, handling errors appropriately.
Process groups allow managing related processes together and are particularly useful for shell job control implementations.
Security Considerations
- Race conditions: Child state may change between checks
- Signal handling: SIGCHLD may interfere with wait3
- Resource limits: Too many processes may cause failures
- Privileges: Requires permission to wait for child processes
- Platform limits: Behavior varies across Unix systems
Best Practices
- Always wait: Prevent zombie process accumulation
- Handle errors: Check for ECHILD and other conditions
- Use WNOHANG: For non-blocking operation when needed
- Check status: Use WIFEXITED/WIFSIGNALED macros
- Monitor resources: Analyze rusage for performance data
Source References
Author
List all Python tutorials.