The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

MojoX::Run - asynchronous external command and subroutine execution for Mojo

SYNOPSIS

# create async executor SINGLETON object
my $mojox_run = MojoX::Run->singleton();

# simple usage
my $pid = $mojox_run->spawn(
	cmd => "ping -W 2 -c 5 host.example.org",
	exit_cb => sub {
		my ($pid, $res) = @_;
		print "Ping finished with exit status $res->{exit_val}.\n";
		print "\tSTDOUT:\n$res->{stdout}\n";
		print "\tSTDERR:\n$res->{stderr}\n";
	}
);
# check for injuries
unless ($pid) {
	print "Command startup failed: ", $mojox_run->error(), "\n";
}

# more complex example...
my $pid2 = $mojox_run->spawn(
	cmd => 'ping -W 2 -c 5 host.example.org',
	stdin_cb => sub {
		my ($pid, $chunk) = @_;
		print "STDOUT $pid: '$chunk'\n"
	},
	# ignore stderr
	stderr_cb => sub {},
	exit_cb => sub {
		my ($pid, $res) = @_;
		print "Process $res->{cmd} [pid: $pid] finished after $res->{time_duration_exec} second(s).\n";
		print "Exit status: $res->{exit_status}";
		print " by signal $res->{exit_signal}" if ($res->{exit_signal});
		print " with coredump." if ($res->{exit_core});
		print "\n";
	}
);

# even fancier usage: spawn coderef
my $pid3 = $mojox_run->spawn(
	cmd => sub {
		for (my $i = 0; $i < 10; $i++) {
			if (rand() > 0.5) {
				print STDERR rand(), "\n"
			} else {
				print rand(), "\n";
			}
			sleep int(rand(10));
		}
		exit (rand() > 0.5) ? 0 : 1;
	},
	exit_cb => {
		print "Sub exited with $res->{exit_status}, STDOUT: $res->{stdout}\n";
	},
);

SIGCHLD WARNING

Object instance of this class takes over SIGCHLD signal handler. You have been warned!

OBJECT CONSTRUCTOR

new

Alias for "singleton" method - object constructor always returns the same object instance.

This restriction is enforced becouse there can be only one active SIGCHLD signal handler per process. However this shouldn't be a problem becouse you can run multiple external processes simultaneously with MojoX::Run :)

singleton

my $mojox_run = MojoX::Run->singleton();

Returns singleton object instance of MojoX::Run. Singleton object uses Mojo's Mojo::IOLoop singleton instance. This is probably what you want instead of creating your own private instance.

METHODS

error

my $err = $mojox_run->error();

Returns last error.

spawn

my $pid = $mojox_run->spawn(%opt);

Spawns new subprocess. The following options are supported:

cmd (string/arrayref/coderef, undef, required):

Command to be started. Command can be simple scalar, array reference or perl CODE reference if you want to custom perl subroutine asynchronously.

stdout_cb (coderef, undef):

Code that will be invoked when data were read from processes's stdout. If omitted, stdout output will be returned as argument to exit_cb. Example:

stdout_cb => sub {
	my ($pid, $data) = @_;
	print "Process $pid stdout: $data";
}
stderr_cb (coderef, undef):

Code that will be invoked when data were read from processes's stderr. If omitted, stderr output will be returned as argument to exit_cb. Example:

stderr_cb => sub {
	my ($pid, $data) = @_;
	print "Process $pid stderr: $data";
}
stdin_cb (coderef, undef):

Code that will be invoked when data wrote to process's stdin were flushed. Example:

stdin_cb => sub {
	my ($pid) = @_;
	print "Process $pid: stdin was flushed.";
}
exit_cb (coderef, undef, required)

Code to be invoked after process exits and all handles have been flushed. Function is called with 2 arguments: Process identifier (pid) and result structure. Example:

exit_cb => sub {
	my ($pid, $res) = @_;
	print "Process $pid exited\n";
	print "Execution error: $res->{error}\n" if (defined $res->{error});
	print "Exit status: $pid->{exit_status}\n";
	print "Killed by signal $pid->{exit_signal}\n" if ($res->{exit_signal});
	print "Process dumped core.\n" if (res->{exit_core});
	print "Process was started at: $res->{time_started}\n";
	print "Process exited at $res->{time_stopped}\n";
	print "Process execution duration: $res->{time_duration_exec}\n";
	print "Execution duration: $res->{time_duration_total}\n";
	print "Process stdout: $res->{stdout}\n";
	print "Process stderr: $res->{stderr}\n";
}
exec_timeout (float, 0):

If set to positive non-zero value, process will be killed after specified timeout of seconds. Timeout accuracy depends on IOLoop's timeout() value (Default is 0.25 seconds).

Returns non-zero process identifier (pid) on success, otherwise 0 and sets error.

spawn_sub

my $code = sub { return { a => 1, b => 2} };
my $pid = $mojox_run->spawn_sub(
	$code,
	exit_cb => sub {
		my ($pid, $result, $exception) = @_;
	}
);

Spawns new subprocess in which $code subroutine will be executed. Return value of subroutine will be delivered to exit_cb callback.

The following options are supported:

stdin_cb (coderef, undef):

Code that will be invoked when data wrote to process's stdin were flushed. Example:

stdin_cb => sub {
	my ($pid) = @_;
	print "Process $pid: stdin was flushed.";
}
exit_cb (coderef, undef, required)

Code to be invoked after process exits and all handles have been flushed. Function is called with 2 arguments: Process identifier (pid) and result structure. Example:

exit_cb => sub {
	my ($pid, $result, $exception) = @_;
	if ($exception) {
		print "Horrible exception accoured while executing subroutine: $exception";
		return;
	}
	
	# result is always arrayref, becouse subs can return list values!
	print "Got async sub result: ", Dumper($result), "\n";
}
exec_timeout (float, 0):

If set to positive non-zero value, process will be killed after specified timeout of seconds. Timeout accuracy depends on IOLoop's timeout() value.

Returns non-zero process identifier (pid) on success, otherwise 0 and sets error.

stdin_write

$mojox_run->stdin_write($pid, $data [, $cb]);

Writes $data to stdin of process $pid if process still has opened stdin. If $cb is defined code reference it will invoke it when data has been written. If $cb is omitted stdin_cb will be invoked if is set for process $pid.

Returns 1 on success, otherwise 0 and sets error.

stdout_cb

# set
$mojox_run->stdout_cb($pid, $cb);
# get
my $cb = $mojox_run->stdout_cb($pid);

If called without $cb argument returns stdout callback for process $pid, otherwise sets stdout callback. If $cb is undefined, removes callback.

Returns undef on error and sets error message.

stderr_cb

# set
$mojox_run->stderr_cb($pid, $cb);
# get
$cb = $mojox_run->stderr_cb($pid);

If called without $cb argument returns stderr callback for process $pid, otherwise sets stderr callback. If $cb is undefined, removes callback.

Returns undef on error and sets error message.

stdin_cb

# set
$mojox_run->stdin_cb($pid, $cb);
# get
$mojox_run->stdin_cb($pid);

If called without $cb argument returns stdin callback for process $pid, otherwise sets stdin callback. If $cb is undefined, removes callback.

Returns undef on error and sets error message.

stdin_close

$mojox_run->stdin_close($pid);

Closes stdin handle to specified process. You need to explicitly close stdin if spawned program doesn't exit until it's stdin is not closed.

stdout_buf

# just get it
$buf = $mojox_run->stdout_buf($pid);
# get and drain
$buf = $mojox_run->stdout_buf($pid, 1);

Returns contents of stdout buffer for process $pid on success, otherwise undef.

Internal buffer is cleared if invoked with non-zero second argument.

stdout_buf_clear

$buf = $mojox_run->stdout_buf_clear($pid);

Clears stdout buffer for process $pid. Returns string containing buffer contents on success, otherwise undef.

stderr_buf

# just get it
$buf = $mojox_run->stderr_buf($pid);
# get and drain
$buf = $mojox_run->stderr_buf($pid, 1);

Returns contents of stderr buffer for process $pid on success, otherwise undef.

Internal buffer is cleared if invoked with non-zero second argument.

stderr_buf_clear

$buf = $mojox_run->stderr_buf_clear($pid);

Clears stderr buffer for process $pid. Returns empty string on success, otherwise undef.

kill

$mojox_run->kill($pid [, $signal = 15]);

Kills process $pid with specified signal. Returns 1 on success, otherwise 0.

log_level ([$level])

Gets or sets loglevel for private logger instance. See Mojo::Log for additional instructions.

num_running

Returns number of currently managed sub-processes.

max_running

$mojox_run->max_running($limit);

Returns currently set concurrently running subprocesses limit if called without arguments. If called with integer argument sets new limit of concurrently spawned external processes and returns old limit.

Value of 0 means that there is no limit.

ioloop

# get
$loop = $mojox_run->ioloop();
# set
$mojox_run->ioloop($loop);

Returns currently used ioloop if called without arguments. Currently used IO loop if changed invoked with initialized Mojo::IOLoop argument - you better be sure what you're doing!

BUGS/CAVEATS

There seem to be problems on some OpenBSD, DragonFly and Solaris systems in conjunction with Mojo::IOLoop implementation. Error manifests itself with the following warning message:

Filehandle GEN3 opened only for input at /usr/libdata/perl5/i386-openbsd/5.10.1/IO/Handle.pm line 465.

IO::Handle's syswrite method is called by Mojo::IOLoop's _write, but there is no good reason to write to process stdout or stderr... I'm investigating, feel free to contact me regarding this issue.

AUTHOR

"Brane F. Gracnar", <"bfg at frost.ath.cx">

BUGS

Please report any bugs or feature requests to bug-mojox-run at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MojoX-Run. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc MojoX::Run

You can also look for information at:

ACKNOWLEDGEMENTS

This module was inspired by POE::Wheel::Run by Rocco Caputo; module includes patched version of IPC::Open3 from Perl distribution which allows perl coderef execution.

LICENSE AND COPYRIGHT

Copyright 2010-2011, Brane F. Gracnar.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.