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 and CODE->(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 $?), or CODE->(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_until runs the queue and waits for events until CODE->() is true (it dies if nothing is left to wait for). poll runs what is ready now, without waiting, and does nothing inside wait_until or a callback.

pipe(overlapped => 'r' | 'w' | 'rw')

A pipe: (READ_HANDLE, WRITE_HANDLE), or nothing with $!. On Windows the ends named by overlapped are 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. Elsewhere pipe(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.