NAME
Linux::Event::Kernel::Process - pidfd process lifecycle and asynchronous stdio
SYNOPSIS
use v5.36;
use Linux::Event::Loop;
use Linux::Event::Kernel::Process;
my $loop = Linux::Event::Loop->new;
my $worker = Linux::Event::Kernel::Process->spawn(
loop => $loop,
command => [$^X, '-e', 'print "hello\\n"'],
stdout => 'pipe',
on_stdout => sub ($process, $bytes) {
print $bytes;
},
on_exit => sub ($process) {
say 'exit code: ' . $process->exit_code
if defined $process->exit_code;
$process->loop->stop;
},
);
$loop->run;
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.
CALLBACKS, SUBCLASSING, AND TUNING
new accepts on_exit and on_error as constructor coderefs. spawn also accepts the stdout, stderr, EOF, and stdin-drain callbacks listed below. Closures are convenient for per-process lexical state; subclasses provide reusable named behavior and a single place for Process I/O tuning:
package BuildProcess;
use parent 'Linux::Event::Kernel::Process';
sub on_stdout ($process, $bytes) { print "build: $bytes" }
sub on_exit ($process) { report_status($process) }
process_options also configures stdin high/low watermarks and the maximum pending stdin bound. Linux::Event validates and caches this policy and the class callbacks once per subclass. Constructor callbacks override same-named methods for one object and are retained once in its effective descriptor; no event-time method lookup or callback-style branch is added.
process_options
Define process_options as a class method on the Process subclass. It returns key/value pairs, or one hash reference:
package BuildProcess;
use parent 'Linux::Event::Kernel::Process';
sub process_options ($class) {
return (
read_size => 131_072,
max_reads_per_tick => 32,
max_pending_stdin => 8_388_608,
);
}
spawn accepts the same names as per-process overrides. The complete option set is:
read_size(default 65,536)Positive maximum byte size of one stdout or stderr callback payload.
max_reads_per_tick(default 64)Positive maximum successful reads from each child output pipe during one readiness dispatch, providing fairness between active resources.
stdin_high_watermark(default 1,048,576)Non-negative pending-stdin byte level at which
write_stdinbegins returning false while still accepting the bytes.stdin_low_watermark(default 262,144)Non-negative pending-stdin byte level at or below which
on_stdin_drainfires after high-watermark backpressure. It must not exceedstdin_high_watermark.max_pending_stdin(default 0)Hard non-negative pending-stdin byte limit. Zero means unbounded.
Linux::Event validates and caches these integer values once per concrete subclass.
SPAWNING
spawn accepts a command argument vector and does not insert a shell:
my $process = Linux::Event::Kernel::Process->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
on_stdout => sub ($process, $bytes) { print $bytes },
on_exit => sub ($process) { $process->loop->stop },
);
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 = Linux::Event::Kernel::Process->new(
pid => $pid,
reap => 1,
on_exit => sub ($process) { ... },
);
$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 subclass defines on_exit($process), or construction supplies on_exit => sub ($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.