os.waitpid
Reap a finished child and find out how it ended. Only waitpid and waitstatus_to_exitcode exist on Windows too; wait, wait3, wait4, waitid, the W* status helpers and flags, the CLD_* codes and the P_* idtypes are Unix only, and pidfd_open needs Linux 5.3+.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| pid | int | yes | waitpid/wait4 on Unix: > 0 that child, 0 any child in my process group, -1 any child, < -1 any child in process group -pid. On Windows: a process handle (as returned by spawn* with P_NOWAIT); <= 0 raises. |
| options | int | yes | 0 for a normal blocking wait, or flags OR-ed together: WNOHANG, WUNTRACED, WCONTINUED (waitid: WEXITED, WSTOPPED, WCONTINUED, WNOHANG, WNOWAIT). Ignored on Windows. |
| status | int | yes | waitstatus_to_exitcode and the W* helpers: a wait status as returned by wait(), waitpid() or os.system() on Unix. |
| idtype, id | int | yes | waitid: P_PID (id is a pid), P_PGID (a process group), P_ALL (id ignored), P_PIDFD (id is a pidfd from pidfd_open, Linux 5.4+). |
Return value
tuple[int, int] | int | bool | waitid_result | None — waitpid/wait: (pid, status). wait3/wait4: (pid, status, rusage). waitid: a waitid_result (or None with WNOHANG). waitstatus_to_exitcode: the exit code, negative for a signal. W* helpers: bool or int. pidfd_open: a file descriptor.
Common patterns
import os _, status = os.waitpid(pid, 0) exit_code = os.waitstatus_to_exitcode(status)
import os while True: try: pid, status = os.waitpid(-1, os.WNOHANG) except ChildProcessError: break if pid == 0: break print(pid, 'ended with', os.waitstatus_to_exitcode(status))
import os if os.WIFEXITED(status): print('exit code', os.WEXITSTATUS(status)) elif os.WIFSIGNALED(status): print('killed by signal', os.WTERMSIG(status), 'core dumped' if os.WCOREDUMP(status) else '') elif os.WIFSTOPPED(status): print('stopped by signal', os.WSTOPSIG(status))
import os fd = os.pidfd_open(pid) info = os.waitid(os.P_PIDFD, fd, os.WEXITED) os.close(fd) if info.si_code == os.CLD_EXITED: print('exit code', info.si_status)
Examples
Pitfalls
import os, sys pid = os.spawnv(os.P_NOWAIT, sys.executable, [sys.executable, '-c', 'raise(SystemExit(7))']) _, status = os.waitpid(pid, 0) status == 7
import os, sys pid = os.spawnv(os.P_NOWAIT, sys.executable, [sys.executable, '-c', 'raise(SystemExit(7))']) _, status = os.waitpid(pid, 0) os.waitstatus_to_exitcode(status) == 7
status = 9 # killed by signal 9 status >> 8
status = 9 sig = status & 0x7f ('killed by signal', sig) if sig else ('exit code', status >> 8)
When to use
- After os.fork or spawn* with P_NOWAIT - every child must be waited for, or it stays a zombie on Unix
- Decoding the status of os.system on Unix (waitstatus_to_exitcode)
- waitid / pidfd_open: race-free waiting on Linux
- Processes started with subprocess → Popen.wait() / .returncode already decode the status
- waitpid(-1, ...) inside a library → it can reap children that belong to other code
Notes
FAQ
Waiting needs real child processes, which cannot run inside the page. Only os.waitpid and os.waitstatus_to_exitcode exist on Windows; wait, wait3, wait4, waitid, the W* helpers and constants, CLD_* and P_* are Unix only, and pidfd_open needs Linux 5.3+. The examples decode statuses by hand and wait on a spawned Python child, which gives the same numbers on both systems.