FuguLib::Process(3p) Perl Library Manual FuguLib::Process(3p)

FuguLib::Processspawn, supervise and reap child processes

use FuguLib::Process;

my $result = FuguLib::Process->spawn_command(
    cmd         => [ '/usr/local/bin/mydaemon', '-f' ],
    daemonize   => 1,
    stdout      => '/var/log/mydaemon.log',
    check_alive => 2,
);

FuguLib::Process->terminate($result->{pid}) if $result->{success};

FuguLib::Process covers the parts of running a child process that are easy to get wrong: telling a child that started from one that exited immediately, escalating from SIGTERM to SIGKILL on a schedule, and reaping zombies without blocking.

The module keeps no state; every method is a class method.

(%args) forks, redirects the standard descriptors, and executes a command.

The arguments are as follows:

cmd
Array reference holding the command and its arguments. Required, and must not be empty.
daemonize
If true, the child calls setsid(2) before exec. The default is false.
stdin, stdout, stderr
Paths for the child's standard descriptors. Each defaults to /dev/null.
check_alive
Seconds to wait before confirming the child survived. The default is 1; 0 skips the check and returns as soon as the fork succeeds.
on_success
Code reference called with the child's process ID once the spawn is judged successful.
on_error
Code reference called with a message when the spawn fails.

When check_alive is non-zero the parent sleeps for that many seconds and then makes two tests: a non-blocking waitpid(2), which reaps the child and proves it exited, and kill(2) with signal 0, which proves it is still there. This is what catches an exec that failed for a reason the fork could not report, such as a command that does not exist.

($pid) reports whether a process exists and is not a zombie. A zombie child is reaped as a side effect of the check and then reported as not alive.

terminate($pid, %args) sends SIGTERM, waits, and sends SIGKILL if that was not enough.

The arguments are as follows:

grace_period
Seconds to wait between the two signals. The default is 5.
on_kill
Code reference called once the process is gone.

($pid) reaps one zombie child without blocking.

reap_all() reaps every zombie child that is ready, without blocking, and returns how many there were.

wait_exit($pid, $timeout) polls until the process exits or the timeout expires. $timeout defaults to 30 seconds.

spawn_perl(%args) runs Perl code in a child process, passing through the -I paths the parent picked up from -I, use lib or PERL5LIB, so the child sees the same modules.

code is the program text and args an array reference of arguments for it; every other argument is handed to ().

spawn_command() and spawn_perl() return a hash reference. On success it holds success set to 1 and pid. On failure success is 0 and error describes what went wrong; pid and exit_code are also present when the child was reached and died.

is_alive() returns 1 or 0.

terminate() returns 1 if the process is gone, 0 if it survived SIGKILL.

reap() returns 1 if the child was reaped or was never there, 0 if it is still running. reap_all() returns a count.

wait_exit() returns 1 if the process exited within the timeout, 0 otherwise.

Run a helper and stop it again:

my $r = FuguLib::Process->spawn_command(
    cmd      => [ 'mdnsctl', 'publish', $name, '_hap', 'tcp', $port ],
    on_error => sub ($err) { $log->err('mdnsctl: %s', $err) },
);

FuguLib::Process->terminate($r->{pid}, grace_period => 10)
    if $r->{success};

No method dies. Failures are reported through the returned hash reference or the boolean return value, and through on_error when one was supplied.

kill(2), setsid(2), waitpid(2), FuguLib::Log(3p), FuguLib::State(3p)

Dick Olsson <hi@senzilla.io>

spawn_command() blocks the caller for check_alive seconds. The check is a plain sleep(3), so a program that spawns several children pays for each one in turn.

A child that legitimately finishes inside the check_alive window is reported as a failure, with an error saying it completed immediately and an exit_code of 0. Use check_alive set to 0 for commands that are expected to be short.

is_alive() and reap() call waitpid(2), which only ever reaps children of the calling process. For any other process they fall back to kill(2) with signal 0, which cannot distinguish a live process from a zombie.

Process IDs are recycled. Nothing here can tell the process that was asked about from a later one that inherited its number.

July 27, 2026 OpenBSD