NAME
Linux::Event::Process - pidfd process lifecycle and asynchronous standard I/O
SYNOPSIS
package LE::Compiler;
use parent 'Linux::Event::Process';
sub on_stdout ($process, $bytes) {
print "compiler: $bytes";
}
sub on_stderr ($process, $bytes) {
warn "compiler: $bytes";
}
sub on_exit ($process) {
if (defined(my $code = $process->exit_code)) {
say "compiler exited with $code";
} else {
say "compiler received signal " . $process->term_signal;
}
}
package main;
my $process = $loop->add(LE::Compiler->spawn(
command => ['/usr/bin/cc', '-c', 'example.c'], # required
stdin => 'pipe', # optional; default inherit
stdout => 'pipe', # optional; default inherit
stderr => 'pipe', # optional; default inherit
data => $build_state, # optional
));
DESCRIPTION
Process is the subclassing boundary for Linux pidfd lifecycle notification. It can spawn a child with asynchronously managed standard I/O or observe an existing PID. One Process owns the pidfd, pipe registrations, exit status, and application data as one logical object.
Spawning uses native posix_spawnp; Linux::Event does not run Perl code in a post-fork child. This remains safe after the Loop's resolver workers have started.
SPAWNING
my $process = LE::Worker->spawn(
loop => $loop, # optional: attach immediately
command => ['/usr/bin/worker', '--once'], # required
cwd => '/srv/application', # optional
env => { MODE => 'production' }, # optional: replacement env
stdin => 'pipe', # optional; default inherit
stdout => 'pipe', # optional; default inherit
stderr => 'stdout', # optional; default inherit
data => $state, # optional
);
command is always a nonempty array reference and is passed directly to posix_spawnp. No implicit shell exists. Use an explicit ['/bin/sh', '-c', $script] command when shell interpretation is intended. Command arguments, cwd, and environment names and values are operating- system byte strings; encode character text before supplying them.
Each standard descriptor accepts inherit, pipe, null, or an existing filehandle. stderr additionally accepts stdout. env replaces the child environment rather than merging it. Omit env to inherit the current environment.
Construction is detached and side-effect free. The child starts when Process attaches through loop => $loop or $loop->add($process).
OBSERVING AN EXISTING PROCESS
my $child = $loop->add(LE::Child->new(
pid => $pid, # required
reap => 1, # default
data => $data, # optional
));
reap => 1 requires the PID to be a child of this process and supplies exit details. Use reap => 0 for a non-child; pidfd still reports termination, but status accessors remain undefined.
CALLBACKS
sub on_exit ($process) { # required
say "child " . $process->pid . " finished";
$process->loop->stop;
}
sub on_stdout ($process, $bytes) { # optional with stdout pipe
print "stdout: $bytes";
}
sub on_stderr ($process, $bytes) { # optional with stderr pipe
warn "stderr: $bytes";
}
sub on_stdout_eof ($process) { # optional with stdout pipe
$process->data->{stdout_eof} = 1 if $process->data;
}
sub on_stderr_eof ($process) { # optional with stderr pipe
$process->data->{stderr_eof} = 1 if $process->data;
}
sub on_stdin_drain ($process) { # optional with stdin pipe
$process->data->{stdin_blocked} = 0 if $process->data;
}
sub on_error ($process, $error) { # optional
warn "$error\n";
}
Callback CVs are resolved once per subclass. on_exit runs once after native reaping and after buffered stdout and stderr have been drained. The Process still exposes its Loop during that callback.
Define an output or EOF callback only when the corresponding descriptor uses pipe. Define on_stdin_drain only with stdin => 'pipe'. Process rejects an impossible callback/stdio combination during construction rather than silently omitting events. Without on_error, asynchronous pipe failures are warned and remain available through last_error.
CLASS POLICY
sub process_options ($class) {
return (
read_size => 65_536, # default
max_reads_per_tick => 64, # default
stdin_high_watermark => 1_048_576, # default
stdin_low_watermark => 262_144, # default
max_pending_stdin => 0, # default: unlimited
);
}
Constructor values override these cached defaults for one spawned Process.
STANDARD INPUT
write_stdin($bytes)
Writes immediately when possible and queues the remainder. It returns false after accepted output exceeds the high watermark. A configured hard limit rejects only the new bytes and reports output_limit through on_error. Input must be a byte string.
close_stdin
Finishes queued input, closes the parent's pipe end, and then delivers EOF to the child. New writes are rejected after closing begins. It does not terminate the Process.
pending_stdin_bytes
Returns the number of queued bytes.
PROCESS CONTROL
signal($number)
Sends a positive signal number through pidfd_send_signal and returns the Process. This avoids PID-reuse races. A failed request throws a structured process Linux::Event::Error with operation signal.
There is deliberately no ambiguous cancel method. Signalling a process, closing its input, and observing its eventual exit are separate operations.
STATUS
pid / state / is_running / is_terminal / exited
Return identity and lifecycle information. States are unattached, running, exited, and failed.
exit_code / term_signal / core_dumped / raw_status
After a reaped exit, return decoded and conventional raw wait-status details. Fields that do not apply are undef or false.
loop / data([$value]) / last_error
Return or update ordinary Process context.
OWNERSHIP
The Loop retains a running Process. Releasing an application reference does not kill it. Destroying a Loop or interpreter closes Linux::Event descriptors but never secretly signals a child. Applications must keep the Loop alive or establish their own shutdown policy until every owned child has been reaped.
A spawned Process, or an observed child with reap => 1, owns that child's wait status. Do not concurrently call wait, waitpid, or run a separate SIGCHLD reaper for the same PID. reap => 0 is the notification-only form when another component owns reaping.
PLATFORM
Process requires Linux pidfds and waitid(P_PIDFD); Linux 5.4 or newer is the supported runtime. The build also requires a libc providing posix_spawn_file_actions_addchdir_np, which makes cwd available without running Perl code after fork. These requirements are checked at build or reported explicitly by the pidfd operation.