NAME

Linux::Event::Kernel::Process - pidfd process lifecycle and asynchronous stdio

SYNOPSIS

package Worker;
use parent 'Linux::Event::Kernel::Process';

sub on_stdout ($process, $bytes) {
    print $bytes;
}

sub on_exit ($process) {
    say 'exit code: ' . $process->exit_code
        if defined $process->exit_code;
    $process->loop->stop;
}

package main;
my $worker = $loop->add(Worker->spawn(
    command => ['/usr/bin/example', '--once'],
    stdout  => 'pipe',
    stderr  => 'pipe',
));

DESCRIPTION

Linux::Event::Kernel::Process is the public process leaf. One object combines process creation or observation, pidfd identity-safe lifecycle notification, optional asynchronous stdin/stdout/stderr, decoded exit status, and pidfd-based signaling.

Linux::Event uses posix_spawnp for spawned children and never runs Perl code in a post-fork child. pidfd operations avoid directing signals or lifecycle state at an unrelated process after numeric PID reuse.

SPAWNING

spawn accepts a command argument vector and does not insert a shell:

my $process = Worker->spawn(
    loop    => $loop,                       # optional
    command => ['/usr/bin/make', '-j4'],    # required
    cwd     => '/srv/project',              # optional
    env     => { BUILD_MODE => 'test' },    # optional replacement env
    stdin   => 'pipe',                      # optional
    stdout  => 'pipe',                      # optional
    stderr  => 'pipe',                      # optional
    data    => $state,                      # optional
);

Construction is side-effect free while detached. The child is created when the object attaches through loop => $loop or $loop->add($process). Consequently pid is undefined before attachment.

env replaces the complete environment when supplied; omit it to inherit the current environment. Use an explicit shell in command only when shell syntax is intentionally required.

STANDARD I/O

Each stdio option accepts inherit, pipe, null, or a caller filehandle. stderr may additionally be stdout to merge child stderr into child stdout.

Pipe callbacks are:

sub on_stdout ($process, $bytes) { ... }
sub on_stdout_eof ($process) { ... }
sub on_stderr ($process, $bytes) { ... }
sub on_stderr_eof ($process) { ... }
sub on_stdin_drain ($process) { ... }

Readable child pipes are drained by the native process I/O helper while preserving read_size callback chunking and max_reads_per_tick fairness.

write_stdin($bytes) writes immediately when possible and queues the remainder. High/low watermarks provide cooperative flow control and max_pending_stdin can impose a hard safety bound. close_stdin drains already accepted input, then closes the child's input pipe to deliver EOF.

OBSERVING AN EXISTING PROCESS

An existing PID may be observed instead of spawned:

my $process = Worker->new(
    pid  => $pid,
    reap => 1,
);
$loop->add($process);

reap => 1 is the default and requires a child process whose status this object owns. reap => 0 permits lifecycle notification for a non-child but leaves decoded wait-status fields undefined.

EXIT CALLBACK AND STATUS

A concrete subclass defines on_exit($process). When a reaped child exits, Linux::Event records either exit_code or term_signal, plus the core-dump flag and conventional raw wait status. Remaining available stdout/stderr bytes are drained before on_exit.

The Loop remains available during on_exit and is released after callback completion. Callback exceptions propagate after native cleanup.

SIGNALS

signal($number) uses pidfd_send_signal rather than a bare numeric PID and returns the Process object. Failures are structured Linux::Event::Error values.

There is deliberately no generic cancel. Stopping observation, closing stdin, asking a child to terminate, and confirming process exit are distinct operations. Applications choose an explicit signal and continue running the Loop until on_exit confirms lifecycle completion.

ERRORS AND OWNERSHIP

Optional on_error($process, $error) receives asynchronous process or stdio failures. Without it Linux::Event warns and retains last_error.

The Loop retains a running Process. Destroying the Loop closes Linux::Event resources but does not secretly kill the child. Spawned processes and observed children with reap => 1 exclusively own their wait status; do not also use a competing waitpid or SIGCHLD reaper for the same child.

PLATFORM

Process requires Linux pidfd support and build headers for pidfd_open and pidfd_send_signal. The runtime lifecycle/status path targets Linux 5.4 or newer. The build also requires libc support for posix_spawn_file_actions_addchdir_np.

SEE ALSO

Linux::Event::Loop, docs/PROCESS-DESIGN.md.