NAME
Proc::Spawner::Loop - the one event loop of Proc::Spawner, the same on POSIX and on Windows (EXPERIMENTAL)
SYNOPSIS
use Proc::Spawner::Loop;
my $loop = Proc::Spawner::Loop->new; # the process's one loop
my $out = '';
$loop->read($fh, 65536, sub { my ($bytes, $errno) = @_; $out .= $bytes if defined $bytes; ... });
$loop->write($fh2, "text\n", sub { my $errno = shift; ... });
$loop->child($pid, sub { my $status = shift; ... }); # $? of waitpid
my $t = $loop->after(2.5, sub { ... }); $t->cancel;
$loop->defer(sub { ... }, @args);
$loop->wait_until(sub { $done });
DESCRIPTION
One loop per process: new always returns the same object. Every callback runs from its queue, one at a time, never inside a signal or inside another callback; a callback must not wait (wait_until called from one dies).
read(FD_OR_HANDLE, MAX, CODE)-
One read of at most MAX bytes when there is something to read:
CODE->(BYTES);CODE->(undef)at end of file;CODE->(undef, ERRNO)on an error. Read again for more. write(FD_OR_HANDLE, BYTES, CODE, chunk => N)-
Writes all of BYTES as the reader takes them, then
CODE->(); when a write fails (the reader is gone: EPIPE), the rest is dropped andCODE->(ERRNO). On POSIX a handle is made non-blocking (an fd number is left as it is;chunk: at most N bytes per write, PIPE_BUF for a blocking pipe). child(PID, CODE)-
CODE->(WAIT_STATUS)when the child PID has ended (its status, as$?), orCODE->(undef)if other code took the status first. Only our own children: the loop waits for exactly these pids. after(SECONDS, CODE)-
CODE->()once SECONDS have passed. defer(CODE, ARGS)-
Queues
CODE->(ARGS). wait_until(CODE),poll()-
wait_untilruns the queue and waits for events untilCODE->()is true (it dies if nothing is left to wait for).pollruns what is ready now, without waiting, and does nothing insidewait_untilor a callback. pipe(overlapped => 'r' | 'w' | 'rw')-
A pipe:
(READ_HANDLE, WRITE_HANDLE), or nothing with$!. On Windows the ends named byoverlappedare opened for overlapped I/O (a named pipe of its own): give such an end to no child, which would do synchronous I/O on it; the loop reads and writes it without a helper thread. Elsewherepipe(2).
read, write, child and after return a watch: $w->cancel stops it (its callback is not called), $w->active tells whether it is still pending.
Backends
POSIX (Proc::Spawner::Loop::POSIX): select on the fds, and SIGCHLD while wait_until runs: a C handler writes a byte into a pipe the loop selects on (decisions D1, D3), then waitpid(PID, WNOHANG) for each child watched.
Windows (Proc::Spawner::Loop::Win32, decision W5, the user, 2026-11-08: "Use the Windows' way"): one I/O completion port. A handle opened for overlapped I/O is read and written so; a synchronous one (an anonymous pipe, a pipe end shared with children, a console) by a helper thread, which posts its result to the same port. A child's end is posted there by RegisterWaitForSingleObject on its process handle; its status then comes from waitpid. The wait has no time limit of its own. While wait_until runs, a console control handler of ours (installed then, put back after, as SIGCHLD on POSIX) posts Ctrl-C, Ctrl-Break, closing and shutdown to the port: Windows calls it before Perl's own, so the wait then gives Perl's a moment (200 ms at most) to mark the signal, and Perl runs the %SIG handler as soon as the wait returns. While an alarm is set (Perl's is a timer message), the wait goes in slices of 50 ms, each followed by Perl's check of its messages. $Proc::Spawner::Loop::Win32::CTRL = 0 leaves the console handler out. Proc::Spawner::Loop::pipe makes a pipe whose ends are overlapped where asked.
SEE ALSO
doc/pspawn-loop.md: how the loop works on each platform, and what was checked.