Complete guide to Python's os.waitid function covering process waiting, child process monitoring, and practical examples.
Last modified April 11, 2025
This comprehensive guide explores Python’s os.waitid function, which provides detailed control over waiting for child process state changes. We’ll cover process selection, status retrieval, and practical examples.
The os.waitid function waits for child processes to change state. It offers more control than os.wait or os.waitpid.
Key parameters: idtype (P_PID, P_PGID, P_ALL), id (process ID/group ID), options (WNOHANG, WNOWAIT, etc.), returns siginfo_t structure with details.
This example demonstrates waiting for a specific child process to terminate. We use P_PID as idtype to target a particular process ID.
wait_specific.py
import os import time
pid = os.fork()
if pid == 0: # Child process print(“Child process running”) time.sleep(2) print(“Child process exiting”) os._exit(42) else: # Parent process print(f"Parent waiting for child PID {pid}") siginfo = os.waitid(os.P_PID, pid, os.WEXITED) print(f"Child exited with status {siginfo.si_status}")
The parent creates a child process that sleeps for 2 seconds then exits. The parent waits specifically for this child using its PID.
The WEXITED option indicates we want to wait for termination events. The siginfo_t structure contains the exit status in si_status.
Using WNOHANG option allows non-blocking checks for child process status. This is useful when you need to poll for status while doing other work.
non_blocking.py
import os import time
pid = os.fork()
if pid == 0: # Child process time.sleep(3) os._exit(0)
while True: try: siginfo = os.waitid(os.P_PID, pid, os.WEXITED | os.WNOHANG) print(f"Child exited with status {siginfo.si_status}") break except ChildProcessError: print(“Child still running, doing other work…”) time.sleep(1)
The parent periodically checks the child’s status without blocking. The loop continues until the child exits and waitid returns successfully.
ChildProcessError is raised when WNOHANG is set and no child matches. This is normal behavior for non-blocking polling.
Using P_PGID as idtype allows waiting for any process in a group. This is useful when managing multiple related child processes.
process_group.py
import os import time
pgid = os.getpid() os.setpgid(0, pgid)
for i in range(3): pid = os.fork() if pid == 0: os.setpgid(0, pgid) print(f"Child {i} (PID {os.getpid()}) running") time.sleep(i + 1) print(f"Child {i} exiting") os._exit(i) break
if pid != 0: print(f"Waiting for processes in group {pgid}") siginfo = os.waitid(os.P_PGID, pgid, os.WEXITED) print(f"Process {siginfo.si_pid} exited with status {siginfo.si_status}")
The parent creates a process group and spawns three child processes. It then waits for any process in the group to terminate.
Note that only one process is reported per waitid call. Multiple calls would be needed to wait for all children in the group.
os.waitid provides detailed process information in the siginfo_t structure. This example shows how to access various fields of this structure.
extended_info.py
import os import signal
pid = os.fork()
if pid == 0: # Child process print(“Child running”) time.sleep(2) os._exit(127) else: # Parent process siginfo = os.waitid(os.P_PID, pid, os.WEXITED) print(f"Process {siginfo.si_pid} status:") print(f" Exit status: {siginfo.si_status}") print(f" Signal: {siginfo.si_signo}") print(f" Code: {siginfo.si_code}") print(f" UID: {siginfo.si_uid}") print(f" Timestamp: {siginfo.si_stime}")
The siginfo_t structure contains extensive information about the process. This includes the exit status, signal number, user ID, and timestamps.
Different fields are relevant depending on how the process terminated. For normal exits, si_status contains the exit code.
The WNOWAIT option leaves the child in a waitable state after retrieval. This allows other wait calls to also get the child’s status information.
nowait.py
import os
pid = os.fork()
if pid == 0: # Child process os._exit(99)
siginfo1 = os.waitid(os.P_PID, pid, os.WEXITED | os.WNOWAIT) print(f"First wait: status {siginfo1.si_status}")
siginfo2 = os.waitid(os.P_PID, pid, os.WEXITED) print(f"Second wait: status {siginfo2.si_status}")
try: # Third attempt should fail os.waitid(os.P_PID, pid, os.WEXITED) except ChildProcessError: print(“No more status available”)
The first wait uses WNOWAIT to peek at the status without consuming it. The second wait retrieves the status normally, consuming it.
The third attempt fails because the status was already consumed by the second wait. This demonstrates the effect of WNOWAIT.
os.waitid can detect when processes are stopped or continued using signals. This requires using WSTOPPED or WCONTINUED in the options.
stopped_process.py
import os import signal import time
pid = os.fork()
if pid == 0: # Child process while True: print(“Child running”) time.sleep(1) else: # Parent process time.sleep(1) os.kill(pid, signal.SIGSTOP) print(“Sent SIGSTOP to child”)
siginfo = os.waitid(os.P_PID, pid, os.WSTOPPED)
print(f"Child stopped by signal {siginfo.si_status}")
os.kill(pid, signal.SIGCONT)
print("Sent SIGCONT to child")
siginfo = os.waitid(os.P_PID, pid, os.WCONTINUED)
print("Child continued")
os.kill(pid, signal.SIGTERM)
siginfo = os.waitid(os.P_PID, pid, os.WEXITED)
print("Child terminated")
The parent stops the child with SIGSTOP, waits for the stop event, then continues it with SIGCONT, and finally terminates it.
Each state change is detected using appropriate wait options. WSTOPPED catches stops, WCONTINUED catches resumes, WEXITED catches exits.
Using P_ALL as idtype allows waiting for any child process. This is similar to os.wait but with more detailed information.
wait_any.py
import os import time
for i in range(3): pid = os.fork() if pid == 0: print(f"Child {i} running") time.sleep(i + 1) os._exit(i + 10) break
if pid != 0: for _ in range(3): siginfo = os.waitid(os.P_ALL, 0, os.WEXITED) print(f"Child {siginfo.si_pid} exited with status {siginfo.si_status}")
The parent creates three child processes with different lifetimes. It then waits for each child in turn using P_ALL as the idtype.
The parent makes three waitid calls to catch all child exits. The order of reported children depends on their termination order.
Privilege requirements: Need appropriate permissions to wait for processes
Race conditions: Process state may change between checks
Signal handling: May interfere with other signal handlers
Resource management: Zombie processes if not properly waited for
Platform differences: Behavior may vary between Unix systems
Use specific idtypes: Prefer P_PID or P_PGID over P_ALL when possible
Handle all cases: Account for exited, signaled, stopped processes
Clean up zombies: Always wait for child processes to avoid zombies
Check return values: Verify waitid succeeded before using results
Consider alternatives: For simple cases, os.waitpid may suffice
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.