Processes

waitpid#

Wait for a specific child process to terminate and reap it.

waitpid is the targeted counterpart of wait. Where wait reaps any child and blocks until one exits, waitpid lets you name which child (or which group of children) to wait for, and lets you poll instead of block by passing WNOHANG in FLAGS. The exit status of the reaped child lands in $?, identically to wait.

Synopsis#

use POSIX ":sys_wait_h";

waitpid $pid, 0;                   # block until $pid exits
waitpid $pid, WNOHANG;             # poll: is $pid done yet?
waitpid -1,  WNOHANG;              # reap any ready zombie, don't block

What you get back#

  • The PID of the reaped child on success.

  • 0 when WNOHANG is set and at least one matching child exists but none have terminated yet — i.e. “still running, try again later”.

  • -1 when no child process matches PID (never existed, already reaped, or — on systems that auto-reap — was harvested by the kernel without Perl’s help).

The child’s exit status is written to $? and the raw platform status to ${^CHILD_ERROR_NATIVE} on every successful reap. Decode $? the same way you would after wait or a backtick call:

my $reaped = waitpid $pid, 0;
if ($reaped > 0) {
    my $exit   = $? >> 8;
    my $signal = $? & 127;
    my $core   = $? & 128;
}

What PID means#

PID selects which children waitpid is willing to reap:

  • Positive PID — wait for that specific child. -1 on return means no such child (you already reaped it, or it was never yours).

  • 0 — wait for any child in the current process group. Useful when your program has joined a process group with other cooperating processes and you only want to harvest your own.

  • -1 — wait for any child, same scope as wait.

  • Less than -1 — wait for any child whose process group ID equals the absolute value of PID. waitpid -$pgid, 0 waits for any member of process group $pgid.

FLAGS is a bitmask. The portable flag is WNOHANG, which makes the call non-blocking: if no matching child has exited, waitpid returns 0 immediately instead of sleeping. FLAGS of 0 means “block until something is reapable”, and is implemented on every platform Perl runs on — even those without a native waitpid(2) syscall, where Perl emulates it by remembering exit statuses of children it has already seen.

Global state it touches#

  • $? — set to the exit status of the reaped child (shifted: high byte = exit code, low 7 bits = signal, bit 0x80 = core-dumped flag).

  • ${^CHILD_ERROR_NATIVE} — set to the raw platform status word, before Perl normalises it into $?.

Neither variable is touched when waitpid returns 0 (WNOHANG and nothing ready) or -1 (no matching child).

Examples#

Block until a known child finishes, then inspect its exit code:

my $pid = fork // die "fork: $!";
if ($pid == 0) {
    exec "/usr/bin/gzip", "big.log" or die "exec: $!";
}
waitpid $pid, 0;
die "gzip failed with status ", $? >> 8 if $?;

Poll without blocking — useful inside an event loop or a SIGCHLD handler:

use POSIX ":sys_wait_h";

sub harvest_children {
    while ((my $kid = waitpid -1, WNOHANG) > 0) {
        warn "child $kid exited with ", $? >> 8, "\n";
    }
}

The classic “reap every pending zombie, don’t block” idiom:

use POSIX ":sys_wait_h";
1 while waitpid(-1, WNOHANG) > 0;

Wait only for children in a specific process group. setpgid first, then target the group by negating its ID:

my $leader = fork // die "fork: $!";
if ($leader == 0) {
    setpgid 0, 0;        # become process-group leader
    exec @worker_cmd or die "exec: $!";
}
setpgid $leader, $leader;  # parent matches, races with child

# ... later, reap any member of that group:
while ((my $kid = waitpid -$leader, WNOHANG) > 0) {
    log_exit($kid, $?);
}

Distinguish the three return values in one place:

my $r = waitpid $pid, WNOHANG;
if    ($r == $pid) { finalize_child($pid, $?) }
elsif ($r == 0)    { still_running($pid) }
elsif ($r == -1)   { warn "no such child $pid (already reaped?)" }

Edge cases#

  • WNOHANG requires use POSIX. The constant is not a Perl built-in; it comes from the POSIX module, typically via use POSIX ":sys_wait_h";. Writing a bare integer (waitpid $pid, 1) works on Linux but is not portable and hides intent.

  • $SIG{CHLD} = 'IGNORE' races with waitpid. When you tell the kernel to auto-reap children, there is nothing left for waitpid to find — it returns -1 with $! set to ECHILD. Choose one reaping strategy per program, not both.

  • Already-reaped PIDs return -1. A single child’s PID can only be reaped once; after that, it is forgotten. If you hand the same PID to waitpid twice, the second call returns -1.

  • Negative PID is a process group, not “any but this one”. waitpid -1234, 0 waits for members of process group 1234, not “every child except PID 1234”. Readers unfamiliar with the POSIX convention often misread this.

  • WNOHANG with PID = -1 is the standard “drain the zombie queue” form. Combined with a while loop it reaps every child that has already exited and stops as soon as the kernel has nothing more to hand back.

  • Return value of -1 can also mean auto-reap. On systems configured to reap children automatically (e.g. a $SIG{CHLD} = 'IGNORE' handler installed at startup), waitpid sees no child and returns -1 even for a PID you just forked. See perlipc for the platform notes.

  • Blocking wait is always available. Even on platforms without a native waitpid(2) or wait4(2) syscall, waitpid PID, 0 works — Perl emulates it by retaining the statuses of children it has already collected.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • wait — block for any child; equivalent to waitpid -1, 0 on systems that support it

  • fork — create the children you will later reap with waitpid

  • kill — send a signal to the same PIDs and process groups waitpid accepts

  • $? — exit status of the reaped child; decode as $? >> 8 for the exit code, $? & 127 for the signal

  • ${^CHILD_ERROR_NATIVE} — raw platform status word for the same child

  • POSIX — source of WNOHANG and the rest of :sys_wait_h