NAME

IO::Async::Open3::Simple - Interface to open3 under IO::Async

VERSION

version 0.01

SYNOPSIS

use v5.42;
use IO::Async::Loop;
use IO::Async::Open3::Simple;

my $loop = IO::Async::Loop->new;
my $done = $loop->new_future;

my $ipc = IO::Async::Open3::Simple->new(
  on_start => sub ($proc, $program, @args) {
    # $proc    isa IO::Async::Open3::Simple::Process
    # $program is a string
    # @args    is the list of arguments
    say 'child PID: ', $proc->pid;
  },
  on_stdout => sub ($proc, $line) {
    say 'out: ', $line;
  },
  on_stderr => sub ($proc, $line) {
    say 'err: ', $line;
  },
  on_exit => sub ($proc, $exit_value, $signal) {
    say 'exit value: ', $exit_value;
    say 'signal:     ', $signal;
    $done->done;
  },
  on_error => sub ($error, $program, @args) {
    # $error is the exception thrown by IPC::Open3::open3
    warn "error: $error";
    $done->done;
  },
);

$ipc->run('echo', 'hello there');
$done->get;

DESCRIPTION

This module provides an interface to open3 while running under IO::Async that delivers data from stdout and stderr as lines are written by the subprocess. The interface is reminiscent of IPC::Open3::Simple, although this module does provide a somewhat different API, so it cannot be used as a drop in replacement for that module.

It is intended as a drop in replacement for AnyEvent::Open3::Simple, for code that would rather use IO::Async as its event loop. Aside from the AnyEvent specific parts (the implementation attribute and the ANYEVENT_OPEN3_SIMPLE environment variable, neither of which apply here), the API is the same.

IO::Async comes with a robust interface to do the same thing as this module: IO::Async::Process, which you more than likely want to use instead. This module is primarily intended for applications that are already using AnyEvent::Open3::Simple and only want to change the underlying event loop.

CONSTRUCTOR

Constructor takes a hash or hashref of event callbacks and attributes. Event callbacks have an on_ prefix, attributes do not.

ATTRIBUTES

  • loop

    The IO::Async::Loop to use. If not provided the shared loop returned by IO::Async::Loop->new is used, which is almost always what you want.

  • implementation

    Accepted and ignored for compatibility with AnyEvent::Open3::Simple. Under IO::Async there is only one implementation: an IO::Async::Stream for each of stdout and stderr, and $loop->watch_process to detect process termination.

EVENTS

These events will be triggered by the subprocess when the run method is called. Each event callback (except on_error) gets passed in an instance of IO::Async::Open3::Simple::Process as its first argument which can be used to get the PID of the subprocess, or to write to it. on_error does not get a process object because it indicates an error in the creation of the process.

Not all of these events will fire depending on the execution of the child process. In the very least exactly one of on_start or on_error will be called.

  • on_start ($proc, $program, @arguments)

    Called after the process is created, but before the run method returns (that is, it does not wait to re-enter the event loop first).

    This event also gets the program name and arguments passed into the run method.

  • on_error ($error, $program, @arguments)

    Called when there is an execution error, for example, if you ask to run a program that does not exist. No process is passed in because the process failed to create. The error passed in is the error thrown by IPC::Open3 (typically a string which begins with "open3: ...").

    In some environments open3 is unable to detect exec errors in the child, so you may not be able to rely on this event. It does seem to work consistently on Perl 5.14 or better though.

    Different environments have different ways of handling it when you ask to run a program that doesn't exist. On Linux and Cygwin, this will raise an on_error event, on MSWin32 it will not trigger a on_error and instead cause a normal exit with a exit value of 1.

    This event also gets the program name and arguments passed into the run method.

  • on_stdout ($proc, $line)

    Called on every line printed to stdout by the child process.

  • on_stderr ($proc, $line)

    Called on every line printed to stderr by the child process.

  • on_exit ($proc, $exit_value, $signal)

    Called when the processes completes, either because it called exit, or if it was killed by a signal.

  • on_success ($proc)

    Called when the process returns zero exit value and is not terminated by a signal.

  • on_signal ($proc, $signal)

    Called when the processes is terminated by a signal.

  • on_fail ($proc, $exit_value)

    Called when the process returns a non-zero exit value.

METHODS

run

$ipc->run($program, @arguments);
$ipc->run($program, @arguments, \$stdin);
$ipc->run($program, @arguments, \@stdin);
$ipc->run($program, @arguments, sub {...});
$ipc->run($program, @arguments, \$stdin, sub {...});
$ipc->run($program, @arguments, \@stdin, sub {...});

Start the given program with the given arguments. Returns immediately (it returns the IO::Async::Open3::Simple instance). Any events that have been specified in the constructor (except for on_start) will not be called until the process re-enters the event loop.

You may optionally provide the full content of standard input as a string reference or list reference as the last argument (or second to last if you are providing a callback below). If provided as a list reference, it will be joined by new lines in whatever format is native to your Perl. Currently on (non cygwin) Windows (Strawberry, ActiveState) this is the only way to provide standard input to the subprocess.

Do not mix the use of passing standard input to run and IO::Async::Open3::Simple::Process#print or IO::Async::Open3::Simple::Process#say, otherwise bad things may happen.

You may provide a callback as the last argument which is called before on_start, and takes the process object as its only argument. For example:

foreach my $i (1..10)
{
  $ipc->run($prog, @args, \$stdin, sub ($proc) {
    $proc->user({ iteration => $i });
  });
}

This is useful for making data accessible to $ipc object's callbacks that may be out of scope otherwise.

CAVEATS

There are some traps for the unwary relating to buffers and deadlocks, IPC::Open3 is recommended reading.

Unlike AnyEvent::Open3::Simple, this module waits for the child's stdout and stderr pipes to reach end of file (in addition to the child process being reaped) before firing on_exit. This guarantees that every line of output is delivered before on_exit, but a grandchild process which inherits and holds open the pipes can delay the event.

If you register a call back for on_exit, but not on_error then use a Future (or condition variable, or $loop->run / $loop->stop) to wait for the process to complete as in this:

my $done = $loop->new_future;
my $ipc = IO::Async::Open3::Simple->new(
  on_exit => sub (@) { $done->done },
);
$ipc->run('command_not_found');
$done->get;

You might be waiting forever if there is an error starting the process (if for example you give it a bad command). To handle this situation you might fail the Future in the event of error:

my $done = $loop->new_future;
my $ipc = IO::Async::Open3::Simple->new(
  on_exit => sub (@) { $done->done },
  on_error => sub ($error, @) {
    $done->fail($error);
  },
);
$ipc->run('command_not_found');
$done->get;

This will cause the get to die, printing a useful diagnostic if the exception isn't caught somewhere else.

Writing to a subprocesses stdin with IO::Async::Open3::Simple::Process#print or IO::Async::Open3::Simple::Process#say is unsupported on Microsoft Windows (it does work under Cygwin though).

SEE ALSO

IO::Async::Open3::Simple::Process

Represents a process being run by this module, typically passed into the callbacks.

AnyEvent::Open3::Simple

The module this one is based on, for use with AnyEvent instead of IO::Async.

IO::Async::Process

Alternative to this module included with IO::Async.

AUTHOR

Graham Ollis <plicease@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Graham Ollis.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.