ZetCode

Python os.waitid Function

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.

Basic Definitions

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.

Waiting for Specific Child Process

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.

Non-blocking Process Waiting

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)

# Parent process
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.

Waiting for Process Group

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

# Create process group
pgid = os.getpid()
os.setpgid(0, pgid)

# Create child processes
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

# Parent waits for any process in group
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.

Getting Extended Process Information

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.

Waiting Without Consuming Status

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)

# First wait with WNOWAIT
siginfo1 = os.waitid(os.P_PID, pid, os.WEXITED | os.WNOWAIT)
print(f"First wait: status {siginfo1.si_status}")

# Second wait without WNOWAIT
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.

Handling Stopped/Continued Processes

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.

Waiting for Any Child Process

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

# Create multiple children
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

# Parent waits for any child
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.

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.