First pass at adding key files
This commit is contained in:
107
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter.pm
Normal file
107
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter.pm
Normal file
@ -0,0 +1,107 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Filter
|
||||
# Author: Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Filter.pm,v 1.3 2004/01/13 01:35:17 jagerman Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
#
|
||||
# Description: Does nothing for now, here as a referance.
|
||||
#
|
||||
|
||||
package GT::IPC::Filter;
|
||||
# ==================================================================
|
||||
|
||||
die "Do not use me";
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use GT::IPC::Filter::Foo;
|
||||
|
||||
my $filter = new GT::IPC::Filter::Foo(sub { my $out = shift ... });
|
||||
# -or-
|
||||
my $filter = new GT::IPC::Filter::Foo(
|
||||
output => sub { my $out = shift; .. },
|
||||
%options
|
||||
);
|
||||
|
||||
$filter->put(\$data);
|
||||
|
||||
$filter->flush;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This documents how to create a filter. The filter system documented here is
|
||||
used for GT::IPC::Run, L<GT::IPC::Run>, currently but could be useful for other
|
||||
things relating to IO and IPC.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
You will need to impliment three methods to create a filter. These methods are
|
||||
pretty simple and strait forward.
|
||||
|
||||
=head2 new
|
||||
|
||||
This is your constructor. You will need to return an object. You should be able
|
||||
to take a sigle argument as well as a hash of options. It isn't manditory but
|
||||
it will keep the filter interface consistent.
|
||||
|
||||
The one argument form of C<new()> is a code reference. This code reference will
|
||||
be called with the data (in whatever form) after you filter it. You should
|
||||
default the rest of your arguments to something reasonable. If there are no
|
||||
reasonable defaults for your options you can stray from this and require the
|
||||
hash form, but you should have a nice error for people that call you with the
|
||||
one argument form:
|
||||
|
||||
$class->fatal(
|
||||
BADARGS => "This class does not accept the one argument form for filters"
|
||||
) if @_ == 1;
|
||||
|
||||
The hash form should take a key C<output> which will be the code reference
|
||||
output will go to once you filter it. The rest of the keys are up to you. Try
|
||||
to have reasonable defaults for the other keys, but fatal if there are any that
|
||||
are required and not present.
|
||||
|
||||
=head2 put
|
||||
|
||||
This method is called with a scaler reference of the data you will be
|
||||
filtering. You are expect to make changes to the data and call the C<output>
|
||||
code reference with the formatted data. For example GT::IPC::Filter::Line
|
||||
calles the C<output> code reference with each line of data, see
|
||||
L<GT::IPC::Filter::Line>. It is ok if you change the scalar reference passed
|
||||
into you.
|
||||
|
||||
=head2 flush
|
||||
|
||||
C<flush()> if called when the stream of data is at an end. Not arguments are
|
||||
passed to it. You are expected send any data you are buffering to the C<output>
|
||||
code reference at this point, after filtering it if nessisary.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
See L<GT::IPC::Run>, L<GT::IPC::Filter::Line>, L<GT::IPC::Filter::Stream>,
|
||||
and L<GT::IPC::Filter::Block>.
|
||||
|
||||
=head1 MAINTAINER
|
||||
|
||||
Scott Beck
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
http://www.gossamer-threads.com/
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Revision: $Id: Filter.pm,v 1.3 2004/01/13 01:35:17 jagerman Exp $
|
||||
|
||||
=cut
|
||||
|
||||
|
154
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter/Block.pm
Normal file
154
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter/Block.pm
Normal file
@ -0,0 +1,154 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Filter::Block
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Block.pm,v 1.7 2004/01/13 01:35:17 jagerman Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
#
|
||||
# Description: Filter streams of input out in block sizes.
|
||||
#
|
||||
|
||||
package GT::IPC::Filter::Block;
|
||||
# ==================================================================
|
||||
|
||||
use strict;
|
||||
use base 'GT::Base';
|
||||
|
||||
sub new {
|
||||
# ----------------------------------------------------------------------------
|
||||
my $class = shift;
|
||||
|
||||
if (@_ == 1) {
|
||||
@_ = (output => $_[0]);
|
||||
}
|
||||
$class->fatal(BADARGS => "Arguments to new() must be a hash")
|
||||
if @_ & 1;
|
||||
my %opts = @_;
|
||||
|
||||
my $output = delete $opts{output};
|
||||
$class->fatal(BADARGS => "No output for new()")
|
||||
unless defined $output;
|
||||
$class->fatal(BADARGS => "No output passed to new() is not a code ref")
|
||||
unless ref($output) eq 'CODE';
|
||||
|
||||
my $block_size = delete $opts{block_size};
|
||||
$block_size = 512 unless defined $block_size;
|
||||
|
||||
return bless {
|
||||
block_size => $block_size,
|
||||
output => $output,
|
||||
}, $class;
|
||||
}
|
||||
|
||||
sub put {
|
||||
# ----------------------------------------------------------------------------
|
||||
my ($self, $in) = @_;
|
||||
|
||||
if (defined $self->{buffer}) {
|
||||
$$in = $self->{buffer} . $$in;
|
||||
undef $self->{buffer};
|
||||
}
|
||||
if (length($$in) >= $self->{block_size}) {
|
||||
my $gets = int(length($$in) / $self->{block_size});
|
||||
for (1 .. $gets) {
|
||||
$self->{output}->(substr($$in, 0, $self->{block_size}));
|
||||
substr($$in, 0, $self->{block_size}) = '';
|
||||
}
|
||||
}
|
||||
$self->{buffer} = $$in;
|
||||
}
|
||||
|
||||
sub flush {
|
||||
# ----------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
$self->{output}->($self->{buffer}) if defined $self->{buffer};
|
||||
undef $self->{buffer};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
GT::IPC::Filter::Block - Implements block based filtering for output streams.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use GT::IPC::Filter::Block;
|
||||
|
||||
my $filter = new GT::IPC::Filter::Block(
|
||||
sub { my $block = shift ... }
|
||||
);
|
||||
# -or-
|
||||
my $filter = new GT::IPC::Filter::Block(
|
||||
output => sub { my $out = shift; .. },
|
||||
block_size => 512 # Default
|
||||
);
|
||||
|
||||
$filter->put(\$data);
|
||||
|
||||
$filter->flush;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Implements block based filtering to an output code reference. Used mainly in
|
||||
GT::IPC::Run, L<GT::IPC::Run> for details.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
There are three methods (as with all filters in this class).
|
||||
|
||||
=head2 new
|
||||
|
||||
Takes either a single argument, which is a code reference to call output with,
|
||||
or a hash of options.
|
||||
|
||||
=over 4
|
||||
|
||||
=item output
|
||||
|
||||
This is the code reference you would like called with each block of output.
|
||||
The blocks are stripped of there ending before this is called.
|
||||
|
||||
=item block_size
|
||||
|
||||
This is the size of chunks of data you want your code reference called with. It
|
||||
defaults to 512.
|
||||
|
||||
=back
|
||||
|
||||
=head2 put
|
||||
|
||||
This method takes a stream of data, it converted it into block based data using
|
||||
the C<block_size> you specified and passes each block to the code reference
|
||||
specified by C<new()>, see L<"new">. There is buffering that happens here.
|
||||
|
||||
=head2 flush
|
||||
|
||||
This method should be called last, when the data stream is over. It flushes the
|
||||
remaining buffer out to the code reference.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
See L<GT::IPC::Run>.
|
||||
|
||||
=head1 MAINTAINER
|
||||
|
||||
Scott Beck
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
http://www.gossamer-threads.com/
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Revision: $Id: Block.pm,v 1.7 2004/01/13 01:35:17 jagerman Exp $
|
||||
|
||||
=cut
|
||||
|
176
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter/Line.pm
Normal file
176
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter/Line.pm
Normal file
@ -0,0 +1,176 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Filter::Line
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Line.pm,v 1.7 2004/01/13 01:35:17 jagerman Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
#
|
||||
# Description: Filter streams of input out to a line.
|
||||
#
|
||||
|
||||
package GT::IPC::Filter::Line;
|
||||
# ==================================================================
|
||||
|
||||
use strict;
|
||||
use base 'GT::Base';
|
||||
|
||||
sub new {
|
||||
# ----------------------------------------------------------------------------
|
||||
my $class = shift;
|
||||
|
||||
if (@_ == 1) {
|
||||
@_ = (output => $_[0]);
|
||||
}
|
||||
$class->fatal(BADARGS => "Arguments to new() must be a hash")
|
||||
if @_ & 1;
|
||||
my %opts = @_;
|
||||
|
||||
my $output = delete $opts{output};
|
||||
$class->fatal(BADARGS => "No output for new()")
|
||||
unless defined $output;
|
||||
$class->fatal(BADARGS => "No output passed to new() is not a code ref")
|
||||
unless ref($output) eq 'CODE';
|
||||
|
||||
my $regex = delete $opts{regex};
|
||||
my $literal = delete $opts{literal};
|
||||
|
||||
$class->fatal(BADARGS => "You can only specify one of literal and regex")
|
||||
if defined $regex and defined $literal;
|
||||
|
||||
if (defined $literal) {
|
||||
$regex = quotemeta $literal;
|
||||
}
|
||||
|
||||
if (!defined $regex) {
|
||||
$regex = '\x0D\x0A?|\x0A\x0D?';
|
||||
}
|
||||
|
||||
return bless {
|
||||
regex => $regex,
|
||||
output => $output,
|
||||
}, $class;
|
||||
}
|
||||
|
||||
sub put {
|
||||
# ----------------------------------------------------------------------------
|
||||
my ($self, $in) = @_;
|
||||
|
||||
if (defined $self->{buffer}) {
|
||||
$$in = $self->{buffer} . $$in;
|
||||
undef $self->{buffer};
|
||||
}
|
||||
my $regex = $self->{regex};
|
||||
my @in = split /($regex)/ => $$in;
|
||||
|
||||
# Not a complete line
|
||||
if ($in[$#in] !~ /$regex/) {
|
||||
$self->{buffer} = pop @in;
|
||||
}
|
||||
|
||||
for (my $i = 0; $i < $#in; $i += 2) {
|
||||
$self->{output}->($in[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
sub flush {
|
||||
# ----------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
$self->{output}->($self->{buffer}) if defined $self->{buffer};
|
||||
undef $self->{buffer};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
GT::IPC::Filter::Line - Implements line based filtering for output streams.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use GT::IPC::Filter::Line;
|
||||
|
||||
my $filter = new GT::IPC::Filter::Line(
|
||||
sub { my $line = shift ... }
|
||||
);
|
||||
# -or-
|
||||
my $filter = new GT::IPC::Filter::Line(
|
||||
output => sub { my $out = shift; .. },
|
||||
regex => '\r?\n'
|
||||
);
|
||||
|
||||
$filter->put(\$data);
|
||||
|
||||
$filter->flush;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Implements line based filtering to an output code reference. Used mainly in
|
||||
GT::IPC::Run, L<GT::IPC::Run> for details.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
There are three methods (as with all filters in this class).
|
||||
|
||||
=head2 new
|
||||
|
||||
Takes either a single argument, which is a code reference to call output with,
|
||||
or a hash of options.
|
||||
|
||||
=over 4
|
||||
|
||||
=item output
|
||||
|
||||
This is the code reference you would like called with each line of output. The
|
||||
lines are stripped of there ending before this is called.
|
||||
|
||||
=item regex
|
||||
|
||||
Specify the regex to use in order to determine the end of line sequence. This
|
||||
regex is used in a split on the input stream. If you capture in this regex it
|
||||
will break the output.
|
||||
|
||||
=item literal
|
||||
|
||||
Specifies a literal new line sequence. The only difference between this option
|
||||
and the C<regex> option is it is C<quotemeta>, See L<perlfunc/quotemeta>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 put
|
||||
|
||||
This method takes a stream of data, it converted it into line based data and
|
||||
passes each line to the code reference specified by C<new()>, see L<"new">.
|
||||
There is buffering that happens here because we have no way of knowing if the
|
||||
output stream does not end with a new line, also streams almost always get
|
||||
partial lines.
|
||||
|
||||
=head2 flush
|
||||
|
||||
This method should be called last, when the data stream is over. It flushes the
|
||||
remaining buffer out to the code reference.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
See L<GT::IPC::Run>.
|
||||
|
||||
=head1 MAINTAINER
|
||||
|
||||
Scott Beck
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
http://www.gossamer-threads.com/
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Revision: $Id: Line.pm,v 1.7 2004/01/13 01:35:17 jagerman Exp $
|
||||
|
||||
=cut
|
||||
|
127
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter/Stream.pm
Normal file
127
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Filter/Stream.pm
Normal file
@ -0,0 +1,127 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Filter::Stream
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Stream.pm,v 1.5 2006/05/26 21:56:30 brewt Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
#
|
||||
# Description: Filter streams of input out to a streams ;).
|
||||
#
|
||||
|
||||
package GT::IPC::Filter::Stream;
|
||||
# ==================================================================
|
||||
|
||||
use strict;
|
||||
use base 'GT::Base';
|
||||
|
||||
sub new {
|
||||
# ----------------------------------------------------------------------------
|
||||
my $class = shift;
|
||||
|
||||
if (@_ == 1) {
|
||||
@_ = (output => $_[0]);
|
||||
}
|
||||
$class->fatal(BADARGS => "Arguments to new() must be a hash")
|
||||
if @_ & 1;
|
||||
my %opts = @_;
|
||||
|
||||
my $output = delete $opts{output};
|
||||
$class->fatal(BADARGS => "No output for new()")
|
||||
unless defined $output;
|
||||
$class->fatal(BADARGS => "No output passed to new() is not a code ref")
|
||||
unless ref($output) eq 'CODE';
|
||||
|
||||
return bless { output => $output }, $class;
|
||||
}
|
||||
|
||||
sub put {
|
||||
# ----------------------------------------------------------------------------
|
||||
my ($self, $in) = @_;
|
||||
|
||||
$self->{output}->($$in);
|
||||
}
|
||||
|
||||
sub flush {
|
||||
# ----------------------------------------------------------------------------
|
||||
# Does nothing
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
GT::IPC::Filter::Block - Implements stream based filtering for output streams.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use GT::IPC::Filter::Stream;
|
||||
|
||||
my $filter = new GT::IPC::Filter::Block(
|
||||
sub { my $chunk = shift ... }
|
||||
);
|
||||
# -or-
|
||||
my $filter = new GT::IPC::Filter::Block(
|
||||
output => sub { my $chunk = shift; .. },
|
||||
);
|
||||
|
||||
$filter->put(\$data);
|
||||
|
||||
$filter->flush;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Implements stream based filtering to an output code reference. Used mainly in
|
||||
GT::IPC::Run, L<GT::IPC::Run> for details. Basically just a pass through to
|
||||
your code reference.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
There are three methods (as with all filters in this class).
|
||||
|
||||
=head2 new
|
||||
|
||||
Takes either a single argument, which is a code reference to call output with,
|
||||
or a hash of options.
|
||||
|
||||
=over 4
|
||||
|
||||
=item output
|
||||
|
||||
This is the code reference you would like called with each output.
|
||||
|
||||
=back
|
||||
|
||||
=head2 put
|
||||
|
||||
This method takes a stream of data and passed it strait to your code reference.
|
||||
There is no buffering that happens here.
|
||||
|
||||
=head2 flush
|
||||
|
||||
This method does nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
See L<GT::IPC::Run>.
|
||||
|
||||
=head1 MAINTAINER
|
||||
|
||||
Scott Beck
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
http://www.gossamer-threads.com/
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Revision: $Id: Stream.pm,v 1.5 2006/05/26 21:56:30 brewt Exp $
|
||||
|
||||
=cut
|
||||
|
873
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run.pm
Normal file
873
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run.pm
Normal file
@ -0,0 +1,873 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Run
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Run.pm,v 1.22 2006/05/26 21:56:30 brewt Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
#
|
||||
# Description:
|
||||
# Runs programs or code references in parallel
|
||||
#
|
||||
package GT::IPC::Run;
|
||||
|
||||
use strict;
|
||||
use base 'GT::Base';
|
||||
use vars qw/@EXPORT_OK $SYSTEM $DEBUG $ERRORS/;
|
||||
|
||||
use Exporter();
|
||||
use Socket;
|
||||
use Symbol qw/gensym/;
|
||||
use POSIX qw(fcntl_h errno_h :sys_wait_h);
|
||||
|
||||
use GT::IPC::Filter::Line;
|
||||
use GT::IPC::Run::Select;
|
||||
use GT::IPC::Run::Child;
|
||||
|
||||
my $can_run_socket = undef;
|
||||
|
||||
*import = \&Exporter::import;
|
||||
@EXPORT_OK = qw/run/;
|
||||
$DEBUG = 0;
|
||||
|
||||
sub READ_BLOCK () { 512 }
|
||||
sub IS_WIN32 () { $^O eq 'MSWin32' }
|
||||
|
||||
$ERRORS = {
|
||||
SEMAPHORE => "Could not create semephore socket; Reason: %s",
|
||||
FORK => "Could not fork; Reason: %s"
|
||||
};
|
||||
|
||||
BEGIN {
|
||||
# http://support.microsoft.com/support/kb/articles/Q150/5/37.asp
|
||||
# defines EINPROGRESS as 10035. We provide it here because some
|
||||
# Win32 users report POSIX::EINPROGRESS is not vendor-supported.
|
||||
if (IS_WIN32) {
|
||||
eval '*EINPROGRESS = sub { 10036 };';
|
||||
eval '*EWOULDBLOCK = sub { 10035 };';
|
||||
eval '*F_GETFL = sub { 0 };';
|
||||
eval '*F_SETFL = sub { 0 };';
|
||||
require GT::IPC::Run::Win32;
|
||||
import GT::IPC::Run::Win32;
|
||||
$SYSTEM = 'GT::IPC::Run::Win32';
|
||||
}
|
||||
else {
|
||||
require GT::IPC::Run::Unix;
|
||||
import GT::IPC::Run::Unix;
|
||||
$SYSTEM = 'GT::IPC::Run::Unix';
|
||||
}
|
||||
}
|
||||
|
||||
sub new {
|
||||
# ------------------------------------------------------------------------
|
||||
my $self = bless {}, $SYSTEM;
|
||||
$self->{select} = new GT::IPC::Run::Select;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub run {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($program, $out, $err, $in) = @_;
|
||||
my $self = new GT::IPC::Run;
|
||||
my $ref;
|
||||
|
||||
$self->fatal("No program specified to start")
|
||||
unless defined $program;
|
||||
$ref = ref $program;
|
||||
$self->fatal("Invalid program passed to start $program")
|
||||
if
|
||||
$ref ne 'CODE' and
|
||||
$ref ne 'ARRAY' and
|
||||
$ref;
|
||||
|
||||
$ref = defined($out) ? ref($out) : undef;
|
||||
my $out_is_handle = _is_handle($out);
|
||||
$self->fatal(
|
||||
BADARGS => "stdout handler is not a code ref or scalar ref"
|
||||
) if
|
||||
defined $ref and
|
||||
$ref ne 'CODE' and
|
||||
$ref ne 'SCALAR' and
|
||||
!$out_is_handle and
|
||||
$ref !~ /\AGT::IPC::Filter::/;
|
||||
|
||||
$ref = defined($err) ? ref($err) : undef;
|
||||
my $err_is_handle = _is_handle($err);
|
||||
$self->fatal(
|
||||
BADARGS => "stderr handler is not a code ref or scalar ref"
|
||||
) if
|
||||
defined $ref and
|
||||
$ref ne 'CODE' and
|
||||
$ref ne 'SCALAR' and
|
||||
!$err_is_handle and
|
||||
$ref !~ /\AGT::IPC::Filter::/;
|
||||
|
||||
$ref = ref $in;
|
||||
my $in_is_handle = _is_handle($in);
|
||||
$self->fatal(
|
||||
BADARGS => "stdin handler is not a scalar ref or filehandle"
|
||||
) if
|
||||
$ref ne 'SCALAR' and
|
||||
!$in_is_handle and
|
||||
$ref !~ /\AGT::IPC::Filter::/ and
|
||||
defined $in;
|
||||
|
||||
my $pid = $self->start(
|
||||
program => $program,
|
||||
stdout => $out,
|
||||
stderr => $err,
|
||||
stdin => $in,
|
||||
debug => $DEBUG
|
||||
);
|
||||
1 while $self->do_one_loop;
|
||||
my $exit_code = $self->exit_code($pid);
|
||||
return $exit_code;
|
||||
}
|
||||
|
||||
sub start {
|
||||
# ------------------------------------------------------------------------
|
||||
my $self = shift;
|
||||
$self->fatal(BADARGS => "Arguments to start() must be a hash")
|
||||
if @_ & 1;
|
||||
my %opts = @_;
|
||||
my $ref;
|
||||
|
||||
$self->{_debug} = delete $opts{debug};
|
||||
$self->{_debug} = $DEBUG unless defined $self->{_debug};
|
||||
|
||||
my $program = delete $opts{program};
|
||||
$self->fatal("No program specified to start")
|
||||
unless defined $program;
|
||||
$ref = ref $program;
|
||||
$self->fatal("Invalid program passed to start $program")
|
||||
if
|
||||
$ref ne 'CODE' and
|
||||
$ref ne 'ARRAY' and
|
||||
$ref;
|
||||
|
||||
my $out = delete $opts{stdout};
|
||||
my $actual_out;
|
||||
$ref = defined($out) ? ref($out) : undef;
|
||||
my $out_is_handle = _is_handle($out);
|
||||
|
||||
# Default to line filter for stderr
|
||||
if ($ref and $ref eq 'CODE') {
|
||||
$actual_out = new GT::IPC::Filter::Line($out);
|
||||
}
|
||||
elsif ($ref and $ref eq 'SCALAR') {
|
||||
$actual_out = new GT::IPC::Filter::Line(sub { $$out .= "$_[0]\n" });
|
||||
}
|
||||
elsif ($ref and $ref =~ /\AGT::IPC::Filter::/) {
|
||||
$actual_out = $out;
|
||||
}
|
||||
elsif (defined($out) and !$out_is_handle) {
|
||||
$self->fatal(
|
||||
BADARGS => "stdout handler is not a code ref or scalar ref"
|
||||
);
|
||||
}
|
||||
|
||||
my $err = delete $opts{stderr};
|
||||
my $actual_err;
|
||||
my $err_is_handle = _is_handle($err);
|
||||
$ref = defined($err) ? ref($err) : undef;
|
||||
|
||||
# Default to line filter for stderr
|
||||
if ($ref and $ref eq 'CODE') {
|
||||
$actual_err = new GT::IPC::Filter::Line($err);
|
||||
}
|
||||
elsif ($ref and $ref eq 'SCALAR') {
|
||||
$actual_err = new GT::IPC::Filter::Line(sub { $$err .= "$_[0]\n" });
|
||||
}
|
||||
elsif ($ref and $ref =~ /\AGT::IPC::Filter::/) {
|
||||
$actual_err = $err;
|
||||
}
|
||||
elsif (defined($err) and !$err_is_handle) {
|
||||
$self->fatal(
|
||||
BADARGS => "stderr handler is not a code ref or scalar ref"
|
||||
);
|
||||
}
|
||||
|
||||
my $in = delete $opts{stdin};
|
||||
my $in_is_handle = _is_handle($in);
|
||||
$ref = ref $in;
|
||||
$self->fatal(
|
||||
BADARGS => "stdin handler is not a scalar ref or filehandle"
|
||||
) if
|
||||
$ref ne 'SCALAR' and
|
||||
!$in_is_handle and
|
||||
defined $in;
|
||||
|
||||
|
||||
my $exit_callback = delete $opts{reaper};
|
||||
$self->fatal(
|
||||
BADARGS => "The exit callback specified is not a code reference"
|
||||
) if
|
||||
defined $exit_callback and
|
||||
ref($exit_callback) ne 'CODE';
|
||||
|
||||
my $done_callback = delete $opts{done_callback};
|
||||
$self->fatal(
|
||||
BADARGS => "The done callback specified is not a code reference"
|
||||
) if
|
||||
defined $done_callback and
|
||||
ref($done_callback) ne 'CODE';
|
||||
|
||||
$self->fatal(
|
||||
BADARGS => "Unknown arguments ", join(", ", keys %opts)
|
||||
) if keys %opts;
|
||||
|
||||
# get the sockets we need for stdin/stdout/stderr communication
|
||||
my ($stderr_read, $stderr_write) = $self->oneway;
|
||||
$self->fatal("could not make stderr pipe: $!")
|
||||
unless defined $stderr_read and defined $stderr_write;
|
||||
my ($stdout_read, $stdout_write) = $self->twoway;
|
||||
$self->fatal("could not make stdout pipe: $!")
|
||||
unless defined $stdout_read and defined $stdout_write;
|
||||
my ($stdin_read, $stdin_write) = $self->oneway;
|
||||
$self->fatal("could not make stdin pipes: $!")
|
||||
unless defined $stdin_read and defined $stdin_write;
|
||||
|
||||
# Defaults to blocking
|
||||
$self->stop_blocking($stdout_read);
|
||||
$self->stop_blocking($stdout_write);
|
||||
$self->stop_blocking($stderr_read);
|
||||
$self->stop_blocking($stderr_write);
|
||||
|
||||
# Change the ones they have overridden
|
||||
if ($in_is_handle) {
|
||||
$stdin_read = $in;
|
||||
undef $stdin_write;
|
||||
undef $in;
|
||||
}
|
||||
elsif (!$in) {
|
||||
undef $stdin_write;
|
||||
undef $stdin_read;
|
||||
}
|
||||
if ($out_is_handle) {
|
||||
$stdout_write = $out;
|
||||
undef $stdout_read;
|
||||
undef $out;
|
||||
}
|
||||
elsif (!$out) {
|
||||
undef $stdout_write;
|
||||
undef $stdout_read;
|
||||
}
|
||||
if ($err_is_handle) {
|
||||
$stderr_write = $err;
|
||||
undef $stderr_read;
|
||||
}
|
||||
elsif (!$err) {
|
||||
undef $stderr_write;
|
||||
undef $stderr_read;
|
||||
}
|
||||
|
||||
# Temporary location for these
|
||||
$self->{current_child} = new GT::IPC::Run::Child(
|
||||
program => $program,
|
||||
stderr_read => $stderr_read,
|
||||
stderr_write => $stderr_write,
|
||||
stdout_read => $stdout_read,
|
||||
stdout_write => $stdout_write,
|
||||
stdin_write => $stdin_write,
|
||||
stdin_read => $stdin_read,
|
||||
stdin => $in,
|
||||
handler_stdout => $actual_out,
|
||||
handler_stderr => $actual_err,
|
||||
exit_callback => $exit_callback,
|
||||
done_callback => $done_callback,
|
||||
exit_status => 0,
|
||||
pid => 0
|
||||
);
|
||||
|
||||
# Run the program/code ref
|
||||
my $pid = $self->execute;
|
||||
return $pid;
|
||||
}
|
||||
|
||||
sub do_loop {
|
||||
# ----------------------------------------------------------------------------
|
||||
my ($self, $wait) = @_;
|
||||
1 while $self->do_one_loop($wait);
|
||||
}
|
||||
|
||||
sub exit_code {
|
||||
# ----------------------------------------------------------------------------
|
||||
my ($self, $pid) = @_;
|
||||
$self->fatal( BADARGS => "No pid passed to exit_code" )
|
||||
unless defined $pid;
|
||||
return $self->{goners}{$pid};
|
||||
}
|
||||
|
||||
sub twoway {
|
||||
# ------------------------------------------------------------------------
|
||||
my ( $self, $conduit_type ) = @_;
|
||||
|
||||
# Try UNIX-domain socketpair if no preferred conduit type is
|
||||
# specified, or if the specified conduit type is 'socketpair'.
|
||||
if (
|
||||
(
|
||||
not defined $conduit_type or
|
||||
$conduit_type eq 'socketpair'
|
||||
) and
|
||||
not defined $can_run_socket
|
||||
)
|
||||
{
|
||||
my ($rw1, $rw2) = (gensym, gensym);
|
||||
|
||||
eval {
|
||||
socketpair( $rw1, $rw2, AF_UNIX, SOCK_STREAM, PF_UNSPEC )
|
||||
or die "socketpair 1 failed: $!";
|
||||
};
|
||||
|
||||
# Socketpair succeeded.
|
||||
if ( !length $@ ) {
|
||||
|
||||
$self->debug("Using socketpair for twoway") if $self->{_debug};
|
||||
# It's two-way, so each reader is also a writer.
|
||||
|
||||
select( ( select($rw1), $| = 1 )[0] );
|
||||
select( ( select($rw2), $| = 1 )[0] );
|
||||
return ( $rw1, $rw2, $rw1, $rw2 );
|
||||
}
|
||||
elsif ($DEBUG) {
|
||||
$self->debug("Error with socketpair: $@\n");
|
||||
}
|
||||
}
|
||||
|
||||
# Try the pipe if no preferred conduit type is specified, or if the
|
||||
# specified conduit type is 'pipe'.
|
||||
if (
|
||||
(
|
||||
not defined $conduit_type or
|
||||
$conduit_type eq 'pipe'
|
||||
) and
|
||||
not defined $can_run_socket
|
||||
)
|
||||
{
|
||||
my ($read1, $write1, $read2, $write2) =
|
||||
(gensym, gensym, gensym, gensym);
|
||||
|
||||
eval {
|
||||
pipe($read1, $write1) or die "pipe 1 failed: $!";
|
||||
pipe($read2, $write2) or die "pipe 2 failed: $!";
|
||||
};
|
||||
|
||||
# Pipe succeeded.
|
||||
if (!length $@) {
|
||||
|
||||
$self->debug("Using pipe for twoway") if $self->{_debug};
|
||||
# Turn off buffering. POE::Kernel does this for us, but someone
|
||||
# might want to use the pipe class elsewhere.
|
||||
select((select($write1), $| = 1)[0]);
|
||||
select((select($write2), $| = 1)[0]);
|
||||
return($read1, $write1, $read2, $write2);
|
||||
}
|
||||
elsif ($self->{_debug}) {
|
||||
$self->debug("Error with pipe(): $@");
|
||||
}
|
||||
}
|
||||
|
||||
# Try a pair of plain INET sockets if no preffered conduit type is
|
||||
# specified, or if the specified conduit type is 'inet'.
|
||||
if (
|
||||
(
|
||||
not defined $conduit_type or
|
||||
$conduit_type eq 'inet'
|
||||
) and (
|
||||
$can_run_socket or
|
||||
not defined $can_run_socket
|
||||
)
|
||||
)
|
||||
{
|
||||
my ($rw1, $rw2) = (gensym, gensym);
|
||||
|
||||
# Try using a pair of plain INET domain sockets.
|
||||
eval { ($rw1, $rw2) = $self->make_socket }; # make_socket
|
||||
# returns em
|
||||
# non-blocking
|
||||
|
||||
# Sockets worked.
|
||||
if (!length $@) {
|
||||
|
||||
$self->debug("Using inet socket for twoway") if $self->{_debug};
|
||||
# Try sockets more often.
|
||||
$can_run_socket = 1;
|
||||
|
||||
# Turn off buffering. POE::Kernel does this for us, but someone
|
||||
# might want to use the pipe class elsewhere.
|
||||
select((select($rw1), $| = 1)[0]);
|
||||
select((select($rw2), $| = 1)[0]);
|
||||
|
||||
return($rw1, $rw2, $rw1, $rw2);
|
||||
}
|
||||
elsif ($self->{_debug}) {
|
||||
$self->debug("Error with socket: $@");
|
||||
}
|
||||
|
||||
# Sockets failed. Don't dry them again.
|
||||
}
|
||||
$self->debug("Nothing worked") if $self->{_debug};
|
||||
|
||||
# There's nothing left to try.
|
||||
return(undef, undef, undef, undef);
|
||||
}
|
||||
|
||||
sub oneway {
|
||||
# ------------------------------------------------------------------------
|
||||
my ( $self, $conduit_type ) = @_;
|
||||
|
||||
# Generate symbols to be used as filehandles for the pipe's ends.
|
||||
my $read = gensym;
|
||||
my $write = gensym;
|
||||
|
||||
# Try UNIX-domain socketpair if no preferred conduit type is
|
||||
# specified, or if the specified conduit type is 'socketpair'.
|
||||
if (
|
||||
(
|
||||
not defined $conduit_type or
|
||||
$conduit_type eq 'socketpair'
|
||||
) and
|
||||
not defined $can_run_socket
|
||||
)
|
||||
{
|
||||
|
||||
eval {
|
||||
socketpair($read, $write, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
|
||||
or die "socketpair failed: $!";
|
||||
};
|
||||
|
||||
# Socketpair succeeded.
|
||||
if (!length $@) {
|
||||
|
||||
$self->debug("Using socketpair for oneway") if $self->{_debug};
|
||||
# It's one-way, so shut down the unused directions.
|
||||
shutdown($read, 1);
|
||||
shutdown($write, 0);
|
||||
|
||||
# Turn off buffering. POE::Kernel does this for us, but someone
|
||||
# might want to use the pipe class elsewhere.
|
||||
select((select($write), $| = 1)[0]);
|
||||
return($read, $write);
|
||||
}
|
||||
elsif ($self->{_debug}) {
|
||||
$self->debug("Could not make socketpair: $@");
|
||||
}
|
||||
}
|
||||
|
||||
# Try the pipe if no preferred conduit type is specified, or if the
|
||||
# specified conduit type is 'pipe'.
|
||||
if (
|
||||
(
|
||||
not defined $conduit_type or
|
||||
$conduit_type eq 'pipe'
|
||||
) and
|
||||
not defined $can_run_socket
|
||||
)
|
||||
{
|
||||
|
||||
eval { pipe($read, $write) or die "pipe failed: $!" };
|
||||
|
||||
# Pipe succeeded.
|
||||
if (!length $@) {
|
||||
|
||||
$self->debug("Using pipe for oneway") if $self->{_debug};
|
||||
# Turn off buffering. POE::Kernel does this for us, but
|
||||
# someone might want to use the pipe class elsewhere.
|
||||
select((select($write),$| = 1 )[0]);
|
||||
return($read, $write);
|
||||
}
|
||||
elsif ($self->{_debug}) {
|
||||
$self->debug("Could not make pipe: $@");
|
||||
}
|
||||
}
|
||||
|
||||
# Try a pair of plain INET sockets if no preffered conduit type is
|
||||
# specified, or if the specified conduit type is 'inet'.
|
||||
if (
|
||||
(
|
||||
not defined $conduit_type or
|
||||
$conduit_type eq 'inet'
|
||||
) and (
|
||||
$can_run_socket or
|
||||
not defined $can_run_socket
|
||||
)
|
||||
)
|
||||
{
|
||||
|
||||
# Try using a pair of plain INET domain sockets.
|
||||
eval { ($read, $write) = $self->make_socket };
|
||||
|
||||
if (!length $@) {
|
||||
|
||||
$self->debug("Using inet socket for oneway") if $self->{_debug};
|
||||
# Try sockets more often.
|
||||
$can_run_socket = 1;
|
||||
|
||||
# It's one-way, so shut down the unused directions.
|
||||
shutdown($read, 1);
|
||||
shutdown($write, 0);
|
||||
|
||||
# Turn off buffering. POE::Kernel does this for us, but someone
|
||||
# might want to use the pipe class elsewhere.
|
||||
select((select($write), $| = 1)[0]);
|
||||
return($read, $write);
|
||||
}
|
||||
else {
|
||||
$self->debug("Could not make socket: $@") if $self->{_debug};
|
||||
$can_run_socket = 0;
|
||||
}
|
||||
}
|
||||
$self->debug("Nothing worked") if $self->{_debug};
|
||||
return(undef, undef);
|
||||
}
|
||||
|
||||
|
||||
# Make a socket. This is a homebrew socketpair() for systems that
|
||||
# don't support it. The things I must do to make Windows happy.
|
||||
|
||||
sub make_socket {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
|
||||
### Server side.
|
||||
|
||||
my $acceptor = gensym();
|
||||
my $accepted = gensym();
|
||||
|
||||
my $tcp = getprotobyname('tcp') or die "getprotobyname: $!";
|
||||
socket( $acceptor, PF_INET, SOCK_STREAM, $tcp ) or die "socket: $!";
|
||||
|
||||
setsockopt($acceptor, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die "reuse: $!";
|
||||
|
||||
my $server_addr = inet_aton('127.0.0.1') or die "inet_aton: $!";
|
||||
$server_addr = pack_sockaddr_in( 0, $server_addr ) or die "sockaddr_in: $!";
|
||||
|
||||
bind($acceptor, $server_addr) or die "bind: $!";
|
||||
|
||||
$self->stop_blocking($acceptor);
|
||||
|
||||
$server_addr = getsockname($acceptor);
|
||||
|
||||
listen($acceptor, SOMAXCONN) or die "listen: $!";
|
||||
|
||||
### Client side.
|
||||
|
||||
my $connector = gensym();
|
||||
|
||||
socket($connector, PF_INET, SOCK_STREAM, $tcp) or die "socket: $!";
|
||||
|
||||
$self->stop_blocking($connector);
|
||||
|
||||
unless (connect( $connector, $server_addr)) {
|
||||
die "connect: $!"
|
||||
if $! and ($! != EINPROGRESS) and ($! != EWOULDBLOCK);
|
||||
}
|
||||
|
||||
my $connector_address = getsockname($connector);
|
||||
my ( $connector_port, $connector_addr ) =
|
||||
unpack_sockaddr_in($connector_address);
|
||||
|
||||
### Loop around 'til it's all done. I thought I was done writing
|
||||
### select loops. Damnit.
|
||||
|
||||
my $in_read = '';
|
||||
my $in_write = '';
|
||||
|
||||
vec($in_read, fileno($acceptor), 1) = 1;
|
||||
vec($in_write, fileno($connector), 1) = 1;
|
||||
|
||||
my $done = 0;
|
||||
while ( $done != 0x11 ) {
|
||||
my $hits =
|
||||
select( my $out_read = $in_read, my $out_write = $in_write, undef,
|
||||
5 );
|
||||
|
||||
# For some reason this always dies when called
|
||||
# successivly (quickly) on the 5th or 6th call
|
||||
die "select: $^E" if $hits < 0;
|
||||
#next unless $hits;
|
||||
# try again?
|
||||
# return $self->make_socket unless $hits;
|
||||
|
||||
# Accept happened.
|
||||
if ( vec( $out_read, fileno($acceptor), 1 ) ) {
|
||||
my $peer = accept( $accepted, $acceptor ) or die "accept: $!";
|
||||
my ( $peer_port, $peer_addr ) = unpack_sockaddr_in($peer);
|
||||
|
||||
if ( $peer_port == $connector_port
|
||||
and $peer_addr eq $connector_addr )
|
||||
{
|
||||
vec( $in_read, fileno($acceptor), 1 ) = 0;
|
||||
$done |= 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
# Connect happened.
|
||||
if ( vec( $out_write, fileno($connector), 1 ) ) {
|
||||
$! = unpack( 'i', getsockopt( $connector, SOL_SOCKET, SO_ERROR ) );
|
||||
die "connect: $!" if $!;
|
||||
|
||||
vec( $in_read, fileno($acceptor), 1 ) = 0;
|
||||
$done |= 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
# Turn blocking back on, damnit.
|
||||
$self->start_blocking($accepted);
|
||||
$self->start_blocking($connector);
|
||||
|
||||
return ( $accepted, $connector );
|
||||
}
|
||||
|
||||
sub _is_handle {
|
||||
my $ref = ref($_[0]);
|
||||
return (
|
||||
($ref and $ref eq 'GLOB') or
|
||||
($ref and $_[0] =~ /=GLOB\(/)
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
GT::IPC::Run - Run programs or code in parallel
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use GT::IPC::Run;
|
||||
|
||||
# stderr and stdout filters default to a
|
||||
# GT::IPC::Line::Filter
|
||||
my $exit_code = run
|
||||
'/bin/ls', # Program to run
|
||||
\*stdout_handle, # stdout event
|
||||
\&stderr_handler, # stderr event
|
||||
\$stdin; # stdin
|
||||
|
||||
|
||||
my $io = new GT::IPC::Run;
|
||||
|
||||
use GT::IPC::Filter::Line;
|
||||
|
||||
my $pid = $io->start(
|
||||
stdout => GT::IPC::Filter::Line->new(
|
||||
regex => "\r?\n",
|
||||
output => sub { print "Output: $_[0]\n" }
|
||||
),
|
||||
program => sub { print "I got forked\n" },
|
||||
);
|
||||
|
||||
while ($io->do_one_loop) {
|
||||
if (defined(my $exit = $io->exit_code($pid))) {
|
||||
print "$pid exited ", ($exit>>8), "\n";
|
||||
}
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Module to simplify running a program or code reference in parallel. Allows
|
||||
catching and filtering the output of the program and filtering it.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
GT::IPC::Run will import one function C<run()> if you request it to.
|
||||
|
||||
=head2 run
|
||||
|
||||
Run is a simple interface to running a program or a subroutine in a separate
|
||||
process and catching the output, both stderr and stdout. This function takes
|
||||
four arguments, only the first argument is required.
|
||||
|
||||
=over 4
|
||||
|
||||
=item First Argument
|
||||
|
||||
The first argument to C<run()> is the program to run or the code reference to
|
||||
run. This argument can be one of three things.
|
||||
|
||||
If a code reference if passed as the first argument to C<run()>, GT::IPC::Run
|
||||
will fork off and run the code reference. You SHOULD NOT exit in the code
|
||||
reference if you want your code to work on Windows. Calling C<die()> is ok,
|
||||
as your code is evaled. There are some things you CAN NOT do if you want your
|
||||
code to work on Windows.
|
||||
|
||||
You SHOULD NOT make any calles to C<system()> or C<exec()>. For some reason, on
|
||||
Windows, this breaks filehandle inheritance so all your output from that moment
|
||||
on (including the C<system()> or C<exec()>) call will go to the real output
|
||||
channel, STDERR or STDOUT.
|
||||
|
||||
You SHOULD NOT change STDERR or STDOUT. The child process on Windows can
|
||||
affect the filehandles in the parent. This is probably because of the way
|
||||
C<fork()> on Windows is emulated as threads.
|
||||
|
||||
You probably should not C<fork()> either, though this is not confirmed I
|
||||
really doubt it will work the way you plan.
|
||||
|
||||
If an array reference is passed in it will be dereferenced and passed to
|
||||
C<exec()>. If a scalar is passed in it will be passed to C<exec()>.
|
||||
|
||||
On Windows the arguments are passed to Win32::Process::Create as the program
|
||||
you wish to run. See L<Win32::Process::Create>.
|
||||
|
||||
=item Second Argument
|
||||
|
||||
The second argument to C<run()> is what you want to happen to STDOUT as it
|
||||
comes in. This argument can be one of three things.
|
||||
|
||||
If it is a reference to a GT::IPC::Filter:: class, that will be used to call
|
||||
your code. See L<GT::IPC::Filter> for details.
|
||||
|
||||
If it is a code reference, a new GT::IPC::Filter::Line object will be created
|
||||
and your code reference will be passed in. Exactly:
|
||||
|
||||
$out = GT::IPC::Filter::Line->new($out);
|
||||
|
||||
GT::IPC::Filter::Line will call your code reference for each line of output
|
||||
from the program, the end of the line will be stripped. See
|
||||
L<GT::IPC::Filter::Line> for details.
|
||||
|
||||
If the argument is a scalar reference, again, a new GT::IPC::Filter::Line
|
||||
object will be created. Exactly:
|
||||
|
||||
|
||||
$out = GT::IPC::Filter::Line->new(sub { $$out .= $_[0] });
|
||||
|
||||
|
||||
=item Third Argument
|
||||
|
||||
The third argument to L<run()> is used to handle STDERR if and when what you
|
||||
are running produces it.
|
||||
|
||||
This can be the exact same thing as the second argument, but will work on
|
||||
STDERR.
|
||||
|
||||
=item Forth Argument
|
||||
|
||||
This argument is how to handle STDIN. It may be one of two things.
|
||||
|
||||
If it is a SCALAR, it will be printed to the input of what you are running.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 new
|
||||
|
||||
The is a simple method that takes no arguments and returns a GT::IPC::Run
|
||||
object. It may take options in the future.
|
||||
|
||||
=head2 start
|
||||
|
||||
This is the more complex method to start a program running. When you call this
|
||||
method, the program you specify is started right away and it's PID (process ID)
|
||||
is returned to you. After you call this you will either need to call
|
||||
C<do_loop()> or C<do_one_loop()> to start getting the programs or code
|
||||
references output. See L<"do_loop"> and L<"do_one_loop"> else where in this
|
||||
document.
|
||||
|
||||
This method takes a hash of arguments. The arguments are:
|
||||
|
||||
=over 4
|
||||
|
||||
=item program
|
||||
|
||||
The name of the program, or code reference you wish to run. This is treated
|
||||
the same way as the first argument to L<run()>. See L<"run"> else where in
|
||||
this document for a description of how this argument is treated.
|
||||
|
||||
=item stdout
|
||||
|
||||
This is how you want STDOUT treated. It can be the same things as the second
|
||||
argument to L<run()>. See L<"run"> else where in this document for a
|
||||
description of how this argument is treated.
|
||||
|
||||
=item stderr
|
||||
|
||||
This is how you want STDERR treated. It can be the same things as the third
|
||||
argument to L<run()>. See L<"run"> else where in this document for a
|
||||
description of how this argument is treated.
|
||||
|
||||
=item stdin
|
||||
|
||||
This argument is how to handle STDIN. It may be one of two things. It is
|
||||
treated like the forth argument to L<run()>. See L<"run"> else where in this
|
||||
document for a description of how this argument is treated.
|
||||
|
||||
=item reaper
|
||||
|
||||
This is a code reference that will be ran once a process has exited. Note: the
|
||||
process may not be done sending us STDOUT or STDERR when it exits.
|
||||
|
||||
The code reference is called with the pid as it's first argument and the exit
|
||||
status of the program for its second argument. The exit status is the same as
|
||||
it is returned by waitpid(). The exit status is somewhat fiddled on Windows to
|
||||
act the way you want it to, e.g. C<$exit_status E<gt>E<gt> 8> will be the
|
||||
number the program exited with.
|
||||
|
||||
=item done_callback
|
||||
|
||||
This is a code reference that works similarly to reaper except that it is only
|
||||
called after the child has died AND all STDOUT/STDERR output has been sent,
|
||||
unlike reaper which is called on exit, regardless of any output that may still
|
||||
be pending.
|
||||
|
||||
The code reference is called wih the pid and exit status of the program as its
|
||||
two arguments.
|
||||
|
||||
=back
|
||||
|
||||
=head2 do_one_loop
|
||||
|
||||
This method takes one argument, the time to wait for C<select()> to return
|
||||
something in milliseconds. This does one select loop on all the processes. You
|
||||
will need to called this after you call C<start()>. Typically:
|
||||
|
||||
my $ipc = new GT::IPC::Run;
|
||||
my $pid = $ipc->start(program => 'ls');
|
||||
1 while $ipc->do_one_loop;
|
||||
my $exit_status = $ipc->exit_code($pid);
|
||||
|
||||
|
||||
=head2 do_loop
|
||||
|
||||
This is similar to C<do_one_loop>, except it does not return unless all
|
||||
processes are finished. Almost the same as:
|
||||
|
||||
1 while $ipc->do_one_loop;
|
||||
|
||||
You can pass the wait time to C<do_loop()> and it will be passed on to
|
||||
C<do_one_loop>. The wait time is in milliseconds.
|
||||
|
||||
=head2 exit_code
|
||||
|
||||
This method takes a pid as an argument and returns the exit status of that
|
||||
processes pid. If the process has not exited yet or GT::IPC::Run did not launch
|
||||
the process, returns undefined. The exit code returned by this is the same as
|
||||
returned by waitpid. See L<perlfunc/waitpid> and L<perlfunc/system>.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
See L<perlipc>, L<perlfunc/system>, L<perlfunc/exec>, L<perlfork>, and
|
||||
L<Win32::Process>.
|
||||
|
||||
=head1 MAINTAINER
|
||||
|
||||
Scott Beck
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
http://www.gossamer-threads.com/
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Revision: $Id: Run.pm,v 1.22 2006/05/26 21:56:30 brewt Exp $
|
||||
|
||||
=cut
|
||||
|
47
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Child.pm
Normal file
47
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Child.pm
Normal file
@ -0,0 +1,47 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Run::Child
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Child.pm,v 1.2 2002/04/24 04:07:18 alex Exp $
|
||||
#
|
||||
# Copyright (c) 2000 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
#
|
||||
# Description: Child storrage class
|
||||
#
|
||||
|
||||
package GT::IPC::Run::Child;
|
||||
# ==================================================================
|
||||
|
||||
use strict;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my %self = @_;
|
||||
bless \%self, $class;
|
||||
return \%self;
|
||||
}
|
||||
|
||||
sub program { if (@_ > 1) { $_[0]->{program} = $_[1]; } return $_[0]->{program}; }
|
||||
sub stderr_read { if (@_ > 1) { $_[0]->{stderr_read} = $_[1]; } return $_[0]->{stderr_read}; }
|
||||
sub stderr_write { if (@_ > 1) { $_[0]->{stderr_write} = $_[1]; } return $_[0]->{stderr_write}; }
|
||||
sub stdout_read { if (@_ > 1) { $_[0]->{stdout_read} = $_[1]; } return $_[0]->{stdout_read}; }
|
||||
sub stdout_write { if (@_ > 1) { $_[0]->{stdout_write} = $_[1]; } return $_[0]->{stdout_write}; }
|
||||
sub stdin_read { if (@_ > 1) { $_[0]->{stdin_read} = $_[1]; } return $_[0]->{stdin_read}; }
|
||||
sub stdin_write { if (@_ > 1) { $_[0]->{stdin_write} = $_[1]; } return $_[0]->{stdin_write}; }
|
||||
sub stdin { if (@_ > 1) { $_[0]->{stdin} = $_[1]; } return $_[0]->{stdin}; }
|
||||
sub handler_stdout { if (@_ > 1) { $_[0]->{handler_stdout} = $_[1]; } return $_[0]->{handler_stdout}; }
|
||||
sub handler_stderr { if (@_ > 1) { $_[0]->{handler_stderr} = $_[1]; } return $_[0]->{handler_stderr}; }
|
||||
sub exit_callback { if (@_ > 1) { $_[0]->{exit_callback} = $_[1]; } return $_[0]->{exit_callback}; }
|
||||
sub done_callback { if (@_ > 1) { $_[0]->{done_callback} = $_[1]; } return $_[0]->{done_callback}; }
|
||||
sub exit_status { if (@_ > 1) { $_[0]->{exit_status} = $_[1]; } return $_[0]->{exit_status}; }
|
||||
sub pid { if (@_ > 1) { $_[0]->{pid} = $_[1]; } return $_[0]->{pid}; }
|
||||
sub called_reaper { if (@_ > 1) { $_[0]->{called_reaper} = $_[1]; } return $_[0]->{called_reaper}; }
|
||||
sub process { if (@_ > 1) { $_[0]->{process} = $_[1]; } return $_[0]->{process}; }
|
||||
sub forked { if (@_ > 1) { $_[0]->{forked} = $_[1]; } return $_[0]->{forked}; }
|
||||
sub called_done { if (@_ > 1) { $_[0]->{called_done} = $_[1]; } return $_[0]->{called_done}; }
|
||||
|
||||
1;
|
||||
|
131
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Select.pm
Normal file
131
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Select.pm
Normal file
@ -0,0 +1,131 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Run::Select
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Select.pm,v 1.6 2004/01/13 01:35:17 jagerman Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
#
|
||||
# Description: Select IO for children handles
|
||||
#
|
||||
|
||||
package GT::IPC::Run::Select;
|
||||
# ==================================================================
|
||||
|
||||
use strict;
|
||||
|
||||
use POSIX qw(errno_h);
|
||||
use constants
|
||||
STDOUT_FN => 0,
|
||||
STDERR_FN => 1;
|
||||
|
||||
sub new {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($class) = @_;
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub add_stdout {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $pid, $stdout) = @_;
|
||||
my $bits = delete $self->{vec_bits};
|
||||
$bits = '' unless defined $bits;
|
||||
|
||||
if (defined $stdout) {
|
||||
my $stdout_fn = fileno($stdout);
|
||||
vec($bits, $stdout_fn, 1) = 1;
|
||||
$self->{$pid}[STDOUT_FN] = $stdout_fn;
|
||||
}
|
||||
$self->{vec_bits} = $bits;
|
||||
}
|
||||
|
||||
sub add_stderr {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $pid, $stderr) = @_;
|
||||
my $bits = delete $self->{vec_bits};
|
||||
$bits = '' unless defined $bits;
|
||||
|
||||
if (defined $stderr) {
|
||||
my $stderr_fn = fileno($stderr);
|
||||
vec($bits, $stderr_fn, 1) = 1;
|
||||
$self->{$pid}[STDERR_FN] = $stderr_fn;
|
||||
}
|
||||
$self->{vec_bits} = $bits;
|
||||
}
|
||||
|
||||
sub remove_stdout {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $pid) = @_;
|
||||
my $bits = delete $self->{vec_bits};
|
||||
$bits = '' unless defined $bits;
|
||||
|
||||
my $fn = $self->{$pid}[STDOUT_FN];
|
||||
if (defined $fn) {
|
||||
vec($bits, $fn, 1) = 0;
|
||||
undef $self->{$pid}[STDOUT_FN];
|
||||
}
|
||||
$self->{vec_bits} = $bits;
|
||||
}
|
||||
|
||||
sub remove_stderr {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $pid) = @_;
|
||||
my $bits = delete $self->{vec_bits};
|
||||
$bits = '' unless defined $bits;
|
||||
|
||||
my $fn = $self->{$pid}[STDERR_FN];
|
||||
if (defined $fn) {
|
||||
vec($bits, $fn, 1) = 0;
|
||||
undef $self->{$pid}[STDERR_FN];
|
||||
}
|
||||
$self->{vec_bits} = $bits;
|
||||
}
|
||||
|
||||
sub can_read {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $timeout) = @_;
|
||||
my $bits = delete $self->{vec_bits};
|
||||
my $sbits = $bits;
|
||||
|
||||
local $!;
|
||||
my $nfound;
|
||||
do {
|
||||
$! = 0;
|
||||
$nfound = select($sbits, undef, undef, $timeout);
|
||||
} while $! == EINTR;
|
||||
if (defined $sbits and $nfound > 0) {
|
||||
my (@stdout_waiting, @stderr_waiting);
|
||||
for my $pid (keys %$self ) {
|
||||
my $child = $self->{$pid};
|
||||
if (!defined $self->{$pid}[STDOUT_FN] and !defined $self->{$pid}[STDERR_FN]) {
|
||||
delete $self->{$pid};
|
||||
next;
|
||||
}
|
||||
if (defined $child->[STDOUT_FN] and (!defined $sbits or vec($sbits, $child->[STDOUT_FN], 1))) {
|
||||
push @stdout_waiting, $pid;
|
||||
}
|
||||
if (defined $child->[STDERR_FN] and (!defined $sbits or vec($sbits, $child->[STDERR_FN], 1))) {
|
||||
push @stderr_waiting, $pid;
|
||||
}
|
||||
}
|
||||
if (!@stdout_waiting and !@stderr_waiting) {
|
||||
$self->debug(
|
||||
"Select said we have nfound, but did not find anything pending!"
|
||||
) if $self->{_debug};
|
||||
}
|
||||
$self->{vec_bits} = $bits;
|
||||
return(\@stdout_waiting, \@stderr_waiting);
|
||||
}
|
||||
elsif ($nfound < 0) {
|
||||
$self->debug("Socket error: $!") if $self->{_debug};
|
||||
}
|
||||
$self->{vec_bits} = $bits;
|
||||
return [], [];
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
306
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Unix.pm
Normal file
306
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Unix.pm
Normal file
@ -0,0 +1,306 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Run::Unix
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Unix.pm,v 1.24 2004/02/17 01:33:07 jagerman Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
|
||||
package GT::IPC::Run::Unix;
|
||||
|
||||
use strict;
|
||||
use vars qw/$EVENTS $ERROR_MESSAGE/;
|
||||
use base 'GT::Base';
|
||||
|
||||
use IO::Select;
|
||||
use POSIX qw(fcntl_h errno_h :sys_wait_h);
|
||||
|
||||
sub READ_BLOCK () { 512 }
|
||||
|
||||
@GT::IPC::Run::Unix::ISA = qw(GT::IPC::Run);
|
||||
$ERROR_MESSAGE = 'GT::IPC::Run';
|
||||
|
||||
sub execute {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
|
||||
# unless ($self->{sigchld_installed}) {
|
||||
# $self->{chld_signal} = sub {
|
||||
# my $child;
|
||||
# while (($child = waitpid -1, WNOHANG) > 0) {
|
||||
# $self->{goners}{$child} = $?;
|
||||
# $self->debug(
|
||||
# "forked child $child exited with exit status (".
|
||||
# ($self->{goners}{$child} >> 8).
|
||||
# ")\n"
|
||||
# ) if $self->{_debug};
|
||||
# }
|
||||
# $SIG{CHLD} = $self->{chld_signal};
|
||||
# };
|
||||
# $SIG{CHLD} = $self->{chld_signal};
|
||||
# $self->{sigchld_installed} = 1;
|
||||
# }
|
||||
|
||||
# Create a semaphore pipe. This is used so that the parent doesn't
|
||||
# begin listening until the child's stdio has been set up.
|
||||
my ($child_pipe_read, $child_pipe_write) = $self->oneway;
|
||||
die "Could not create semaphore socket: $!" unless defined $child_pipe_read;
|
||||
|
||||
my $pid;
|
||||
if ($pid = fork) { # Parent
|
||||
my $child = delete $self->{current_child};
|
||||
$self->{select}->add_stdout($pid => $child->stdout_read);
|
||||
$self->{select}->add_stderr($pid => $child->stderr_read);
|
||||
$self->{children}{$pid} = $child;
|
||||
$child->pid($pid);
|
||||
if ($child->stdin and ref($child->stdin) eq 'SCALAR') {
|
||||
print {$child->stdin_write} ${$child->stdin};
|
||||
close $child->stdin_write;
|
||||
}
|
||||
|
||||
# Cludge to stop speed forking
|
||||
select undef, undef, undef, 0.001;
|
||||
|
||||
# Close what the parent will not need
|
||||
# close $child->stdout_write if $child->stdout_write;
|
||||
# close $child->stderr_write if $child->stderr_write;
|
||||
# close $child->stdin_read if $child->stdin_read;
|
||||
<$child_pipe_read>;
|
||||
close $child_pipe_read;
|
||||
close $child_pipe_write;
|
||||
return $pid;
|
||||
}
|
||||
else {
|
||||
$self->fatal(FORK => "$!") unless defined $pid;
|
||||
$self->debug("Forked: $$\n") if $self->{_debug} > 1;
|
||||
|
||||
# Get out self and out filenos
|
||||
my $self = delete $self->{current_child};
|
||||
my ($stdout_fn, $stderr_fn, $stdin_fn);
|
||||
$stdout_fn = fileno($self->stdout_write) if $self->stdout_write;
|
||||
$stderr_fn = fileno($self->stderr_write) if $self->stderr_write;
|
||||
$stdin_fn = fileno($self->stdin_read) if $self->stdin_read;
|
||||
# Close what the child won't need.
|
||||
# close $self->stdin_write if $self->stdin_write;
|
||||
# close $self->stderr_read if $self->stderr_read;
|
||||
# close $self->stdout_read if $self->stdout_read;
|
||||
|
||||
# Tied handles break this
|
||||
untie *STDOUT if tied *STDOUT;
|
||||
untie *STDERR if tied *STDERR;
|
||||
untie *STDIN if tied *STDIN;
|
||||
|
||||
# Redirect STDOUT to the write end of the stdout pipe.
|
||||
if (defined $stdout_fn) {
|
||||
$self->debug("Opening stdout to fileno $stdout_fn") if $self->{_debug};
|
||||
open( STDOUT, ">&$stdout_fn" )
|
||||
or die "can't redirect stdout in child pid $$: $!";
|
||||
$self->debug("stdout opened") if $self->{_debug};
|
||||
}
|
||||
|
||||
# Redirect STDIN from the read end of the stdin pipe.
|
||||
if (defined $stdin_fn) {
|
||||
$self->debug("Opening stdin to fileno $stdin_fn") if $self->{_debug};
|
||||
open( STDIN, "<&$stdin_fn" )
|
||||
or die "can't redirect STDIN in child pid $$: $!";
|
||||
$self->debug("stdin opened") if $self->{_debug};
|
||||
}
|
||||
|
||||
# Redirect STDERR to the write end of the stderr pipe.
|
||||
if (defined $stderr_fn) {
|
||||
$self->debug("Opening stderr to fileno $stderr_fn") if $self->{_debug};
|
||||
open( STDERR, ">&$stderr_fn" )
|
||||
or die "can't redirect stderr in child: $!";
|
||||
}
|
||||
|
||||
select STDERR; $| = 1;
|
||||
select STDOUT; $| = 1;
|
||||
|
||||
# Tell the parent that the stdio has been set up.
|
||||
close $child_pipe_read;
|
||||
print $child_pipe_write "go\n";
|
||||
close $child_pipe_write;
|
||||
|
||||
# Program code here
|
||||
my $program = $self->program;
|
||||
if (ref($program) eq 'ARRAY') {
|
||||
exec(@$program) or do {
|
||||
print STDERR "can't exec (@$program) in child pid $$:$!\n";
|
||||
eval { POSIX::_exit($?); };
|
||||
eval { kill KILL => $$; };
|
||||
};
|
||||
}
|
||||
elsif (ref($program) eq 'CODE') {
|
||||
$? = 0;
|
||||
$program->();
|
||||
|
||||
# In case flushing them wasn't good enough.
|
||||
close STDOUT if defined fileno(STDOUT);
|
||||
close STDERR if defined fileno(STDERR);
|
||||
|
||||
eval { POSIX::_exit($?); };
|
||||
eval { kill KILL => $$; };
|
||||
}
|
||||
else {
|
||||
exec($program) or do {
|
||||
print STDERR "can't exec ($program) in child pid $$:$!\n";
|
||||
eval { POSIX::_exit($?); };
|
||||
eval { kill KILL => $$; };
|
||||
};
|
||||
}
|
||||
die "How did I get here!";
|
||||
}
|
||||
}
|
||||
|
||||
sub put {
|
||||
# ------------------------------------------------------------------------
|
||||
my $self = shift;
|
||||
my $pid = shift;
|
||||
print {$self->{children}{$pid}->stdin_write} @_;
|
||||
}
|
||||
|
||||
sub do_one_loop {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $wait) = @_;
|
||||
$wait = 0.05 unless defined $wait;
|
||||
|
||||
# See if any children have exited
|
||||
my $child;
|
||||
while (($child = waitpid -1, WNOHANG) > 0) {
|
||||
next unless exists $self->{children}{$child};
|
||||
$self->{goners}{$child} = $?;
|
||||
$self->{children}{$child}->exit_status($?);
|
||||
$self->debug(
|
||||
"forked child $child exited with exit status (".
|
||||
($self->{goners}{$child} >> 8).
|
||||
")\n"
|
||||
) if $self->{_debug};
|
||||
}
|
||||
|
||||
for my $pid (keys %{$self->{goners}} ) {
|
||||
my $child = $self->{children}{$pid} or next;
|
||||
if (!$child->called_reaper) {
|
||||
$child->exit_callback->($pid, $self->{goners}{$pid})
|
||||
if $child->exit_callback;
|
||||
$child->called_reaper(1);
|
||||
}
|
||||
}
|
||||
my ($stdout_pending, $stderr_pending) = $self->{select}->can_read($wait);
|
||||
|
||||
my %not_pending = %{$self->{children}};
|
||||
for my $pid (@$stdout_pending, @$stderr_pending) {
|
||||
delete $not_pending{$pid};
|
||||
}
|
||||
for my $pid (keys %{$self->{goners}}) {
|
||||
my $child = $self->{children}{$pid} or next;
|
||||
if ($not_pending{$pid} and not $child->called_done) {
|
||||
$child->done_callback->($pid, $self->{goners}{$pid})
|
||||
if $child->done_callback;
|
||||
$child->called_done(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!@$stdout_pending and !@$stderr_pending) {
|
||||
$self->debug("Nothing else to do, flushing buffers")
|
||||
if $self->{_debug};
|
||||
$self->debug(
|
||||
"Children: ".
|
||||
keys(%{$self->{children}}).
|
||||
"; goners: ".
|
||||
keys(%{$self->{goners}})
|
||||
) if $self->{_debug};
|
||||
|
||||
# We still have children out there
|
||||
return 1 if keys(%{$self->{children}}) > keys(%{$self->{goners}});
|
||||
|
||||
# Flush output filters and delete children to free memory and FDs
|
||||
$self->flush_filters;
|
||||
|
||||
# Nothing left to do
|
||||
return 0;
|
||||
}
|
||||
# else we have stuff to do
|
||||
for my $pid (@$stdout_pending) {
|
||||
my $child = $self->{children}{$pid};
|
||||
$self->debug("STDOUT pending for $pid") if $self->{_debug};
|
||||
|
||||
my $ret = sysread($child->stdout_read, my $buff, READ_BLOCK);
|
||||
if (!$ret) {
|
||||
if ($! != EAGAIN and $! != 0) {
|
||||
# Socket error
|
||||
$self->debug(
|
||||
"$pid: Socket Read: $!: $^E; Errno: ", 0+$!
|
||||
) if $self->{_debug};
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Process callbacks
|
||||
$self->debug("[$pid STDOUT]: `$buff'\n")
|
||||
if $self->{_debug} > 1;
|
||||
if ($child->handler_stdout) {
|
||||
$child->handler_stdout->put(\$buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
for my $pid (@$stderr_pending) {
|
||||
my $child = $self->{children}{$pid};
|
||||
$self->debug("STDERR pending for $pid") if $self->{_debug};
|
||||
|
||||
my $ret = sysread($child->stderr_read, my $buff, READ_BLOCK);
|
||||
if (!$ret) {
|
||||
if ($! != EAGAIN and $! != 0) {
|
||||
# Socket error
|
||||
$self->debug(
|
||||
"$pid: Socket Read: $!: $^E; Errno: ", 0+$!
|
||||
) if $self->{_debug};
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Process callbacks
|
||||
$self->debug("[$pid STDERR]: `$buff'\n")
|
||||
if $self->{_debug} > 1;
|
||||
if ($child->handler_stderr) {
|
||||
$child->handler_stderr->put(\$buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub flush_filters {
|
||||
# ------------------------------------------------------------------------
|
||||
my $self = shift;
|
||||
for my $pid (keys %{$self->{children}}) {
|
||||
my $child = delete $self->{children}{$pid};
|
||||
$self->select->remove_stdout($pid);
|
||||
$self->select->remove_stderr($pid);
|
||||
if ($child->handler_stdout) {
|
||||
$child->handler_stdout->flush;
|
||||
}
|
||||
if ($child->handler_stderr) {
|
||||
$child->handler_stderr->flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub stop_blocking {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $socket_handle) = @_;
|
||||
my $flags = fcntl($socket_handle, F_GETFL, 0) or die "getfl: $!";
|
||||
$flags = fcntl($socket_handle, F_SETFL, $flags | O_NONBLOCK)
|
||||
or die "setfl: $!";
|
||||
}
|
||||
|
||||
sub start_blocking {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $socket_handle) = @_;
|
||||
my $flags = fcntl($socket_handle, F_GETFL, 0) or die "getfl: $!";
|
||||
$flags = fcntl($socket_handle, F_SETFL, $flags & ~O_NONBLOCK)
|
||||
or die "setfl: $!";
|
||||
}
|
||||
|
||||
|
||||
1;
|
505
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Win32.pm
Normal file
505
site/slowtwitch.com/cgi-bin/articles/GT/IPC/Run/Win32.pm
Normal file
@ -0,0 +1,505 @@
|
||||
# ==================================================================
|
||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
|
||||
#
|
||||
# GT::IPC::Run::Win32
|
||||
# Author : Scott Beck
|
||||
# CVS Info : 087,071,086,086,085
|
||||
# $Id: Win32.pm,v 1.16 2006/03/30 18:40:22 sbeck Exp $
|
||||
#
|
||||
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
|
||||
# ==================================================================
|
||||
|
||||
package GT::IPC::Run::Win32;
|
||||
|
||||
use strict;
|
||||
use vars qw/$EVENTS $ERROR_MESSAGE/;
|
||||
use base 'GT::Base';
|
||||
|
||||
use POSIX qw(fcntl_h errno_h :sys_wait_h);
|
||||
use GT::Lock qw/lock unlock/;
|
||||
use Win32;
|
||||
use Win32::Process;
|
||||
use Win32::Mutex;
|
||||
sub READ_BLOCK () { 512 }
|
||||
|
||||
# What Win32 module exports this?
|
||||
sub WSAEWOULDBLOCK () { 10035 }
|
||||
|
||||
@GT::IPC::Run::Win32::ISA = qw(GT::IPC::Run);
|
||||
|
||||
$ERROR_MESSAGE = 'GT::IPC::Run';
|
||||
|
||||
sub execute {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
|
||||
my $pid;
|
||||
my $child = $self->{current_child};
|
||||
if (ref($child->program) eq 'ARRAY' or !ref($child->program)) {
|
||||
my $process = $self->fork_exec;
|
||||
$child->pid($process->GetProcessID);
|
||||
$child->process($process);
|
||||
}
|
||||
else {
|
||||
$child->pid($self->fork_code);
|
||||
$child->forked(1);
|
||||
}
|
||||
$self->{children}{$child->pid} = delete $self->{current_child};
|
||||
return $child->pid;
|
||||
}
|
||||
|
||||
sub put {
|
||||
# ------------------------------------------------------------------------
|
||||
my $self = shift;
|
||||
my $pid = shift;
|
||||
print {$self->{children}{$pid}->stdin_write} @_;
|
||||
}
|
||||
|
||||
sub fork_exec {
|
||||
# ------------------------------------------------------------------------
|
||||
# Called on Win32 systems when wanting to exec() a process. This replaces
|
||||
# forking and executing. You cannot get filehandle inheritance when exec()
|
||||
# after a fork, fun stuff.
|
||||
my $self = shift;
|
||||
|
||||
|
||||
my $child = $self->{current_child};
|
||||
my $process = '';
|
||||
my $program = ref($child->program) eq 'ARRAY'
|
||||
? $child->program
|
||||
: [split ' ', $child->program];
|
||||
open STDOUT_SAVE, ">&STDOUT";
|
||||
open STDERR_SAVE, ">&STDERR";
|
||||
open STDIN_SAVE, "<&STDIN";
|
||||
|
||||
# Redirect STDOUT to the write end of the stdout pipe.
|
||||
if ($child->stdout_write) {
|
||||
my $fn = fileno($child->stdout_write);
|
||||
if (defined $fn) {
|
||||
$self->debug("Opening stdout to fileno $fn") if $self->{_debug};
|
||||
open( STDOUT, ">&$fn" )
|
||||
or die "can't redirect stdout in child pid $$: $!";
|
||||
$self->debug("stdout opened") if $self->{_debug};
|
||||
}
|
||||
else {
|
||||
die "No fileno for stdout_write";
|
||||
}
|
||||
}
|
||||
|
||||
# Redirect STDIN from the read end of the stdin pipe.
|
||||
if ($child->stdin_read) {
|
||||
my $fn = fileno($child->stdin_read);
|
||||
if (defined $fn) {
|
||||
$self->debug("Opening stdin to fileno $fn") if $self->{_debug};
|
||||
open( STDIN, "<&$fn" )
|
||||
or die "can't redirect STDIN in child pid $$: $!";
|
||||
$self->debug("stdin opened") if $self->{_debug};
|
||||
}
|
||||
else {
|
||||
die "No fileno for stdin_read";
|
||||
}
|
||||
}
|
||||
|
||||
# Redirect STDERR to the write end of the stderr pipe.
|
||||
if ($child->stderr_write) {
|
||||
my $fn = fileno($child->stderr_write);
|
||||
if (defined $fn) {
|
||||
$self->debug("Opening stderr to fileno $fn") if $self->{_debug};
|
||||
open( STDERR, ">&$fn" )
|
||||
or die "can't redirect stderr in child: $!";
|
||||
}
|
||||
else {
|
||||
die "No fileno for stderr_write";
|
||||
}
|
||||
}
|
||||
|
||||
select STDOUT; $| = 1;
|
||||
select STDERR; $| = 1;
|
||||
select STDOUT;
|
||||
Win32::Process::Create(
|
||||
$process,
|
||||
$program->[0],
|
||||
"@$program",
|
||||
1,
|
||||
NORMAL_PRIORITY_CLASS,
|
||||
'.'
|
||||
) or do {
|
||||
open STDOUT, ">&STDOUT_SAVE";
|
||||
open STDERR, ">&STDERR_SAVE";
|
||||
open STDIN, "<&STDIN_SAVE";
|
||||
die "can't exec (@$program) using Win32::Process; Reason: ".
|
||||
Win32::FormatMessage(Win32::GetLastError);
|
||||
};
|
||||
syswrite($child->stdin_write, ${$child->stdin}, length(${$child->stdin}), 0)
|
||||
if ref($child->stdin) eq 'SCALAR';
|
||||
open STDOUT, ">&STDOUT_SAVE";
|
||||
open STDERR, ">&STDERR_SAVE";
|
||||
open STDIN, "<&STDIN_SAVE";
|
||||
return $process;
|
||||
}
|
||||
|
||||
sub fork_code {
|
||||
# ------------------------------------------------------------------------
|
||||
my $self = shift;
|
||||
|
||||
# Hack to keep from forking too many process too fast, perl on windows
|
||||
# tends to segv when that happens
|
||||
select undef, undef, undef, 0.5;
|
||||
|
||||
# So we know when the child is finished setting up
|
||||
my $mutex = new Win32::Mutex(1, 'CHILD');
|
||||
my $pid;
|
||||
if ($pid = fork) { # Parent
|
||||
my $child = $self->{current_child};
|
||||
$mutex->wait(2000);
|
||||
print {$child->stdin_write} ${$child->stdin}
|
||||
if ref($child->stdin) eq 'SCALAR';
|
||||
return $pid;
|
||||
}
|
||||
else {
|
||||
$self->fatal( FORK => "$!" ) unless defined $pid;
|
||||
$self->debug("Forked: $$\n") if $self->{_debug} > 1;
|
||||
|
||||
# Hack to keep the child from destroying the mutex
|
||||
{
|
||||
package GT::IPC::Run::Mutex;
|
||||
@GT::IPC::Run::Mutex::ISA = 'Win32::Mutex';
|
||||
sub DESTROY {}
|
||||
}
|
||||
bless $mutex, 'GT::IPC::Run::Mutex';
|
||||
|
||||
my $child = $self->{current_child};
|
||||
my ($stdout, $stderr, $stdin) = (
|
||||
$child->stdout_write,
|
||||
$child->stderr_write,
|
||||
$child->stdin_read
|
||||
);
|
||||
|
||||
# Redirect STDOUT to the write end of the stdout pipe.
|
||||
if (defined $stdout) {
|
||||
*STDOUT = $stdout;
|
||||
$self->debug("stdout opened") if $self->{_debug};
|
||||
}
|
||||
|
||||
# Redirect STDIN from the read end of the stdin pipe.
|
||||
if (defined $stdin) {
|
||||
*STDIN = $stdin;
|
||||
$self->debug("stdin opened") if $self->{_debug};
|
||||
}
|
||||
|
||||
# Redirect STDERR to the write end of the stderr pipe.
|
||||
if (defined $stderr) {
|
||||
*STDERR = $stderr;
|
||||
}
|
||||
|
||||
select STDERR; $| = 1;
|
||||
select STDOUT; $| = 1;
|
||||
|
||||
# Tell the parent that the stdio has been set up.
|
||||
$mutex->release;
|
||||
|
||||
# Launch the code reference
|
||||
$child->program->();
|
||||
close STDOUT if defined fileno STDOUT;
|
||||
close STDERR if defined fileno STDERR;
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
sub do_one_loop {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $wait) = @_;
|
||||
$wait = 0.05 unless defined $wait;
|
||||
|
||||
$self->check_for_exit;
|
||||
$self->debug(
|
||||
"Children: ". keys(%{$self->{children}}).
|
||||
"; goners: ". keys(%{$self->{goners}})
|
||||
) if $self->{_debug};
|
||||
|
||||
for my $pid (keys %{$self->{children}}) {
|
||||
my $child = $self->{children}{$pid};
|
||||
|
||||
if ($child->stdout_read) {
|
||||
my $ret = sysread($child->stdout_read, my $buff, READ_BLOCK);
|
||||
if (!$ret) {
|
||||
# Fun stuff with win32
|
||||
if ($! == EAGAIN) {
|
||||
# Socket error
|
||||
#$self->{select}->remove_stdout($pid);
|
||||
$self->debug(
|
||||
"1: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ".(0+$!)."; OSErrno: ".(0+$^E)
|
||||
) if $self->{_debug};
|
||||
}
|
||||
elsif ($! == WSAEWOULDBLOCK and exists $self->{goners}{$pid}) {
|
||||
$child->{socket_err}++;
|
||||
$self->debug(
|
||||
"2: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ".(0+$!)."; OSErrno: ".(0+$^E)
|
||||
) if $self->{_debug} > 1;
|
||||
}
|
||||
else {
|
||||
$child->{socket_err}++;
|
||||
$self->debug(
|
||||
"2: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ".(0+$!)."; OSErrno: ".(0+$^E)
|
||||
) if $self->{_debug} > 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Process callbacks
|
||||
$self->debug("[$pid STDOUT]: `$buff'\n") if $self->{_debug} > 1;
|
||||
if (defined $child->handler_stdout) {
|
||||
$child->handler_stdout->put(\$buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($child->stderr_read) {
|
||||
my $ret = sysread($child->stderr_read, my $buff, READ_BLOCK);
|
||||
if (!$ret) {
|
||||
# Fun stuff with win32
|
||||
if ($! == EAGAIN) {
|
||||
# Socket error
|
||||
#$self->{select}->remove_stderr($pid);
|
||||
$self->debug(
|
||||
"1: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ".(0+$!)."; OSErrno: ".(0+$^E)
|
||||
) if $self->{_debug};
|
||||
}
|
||||
elsif ($! == WSAEWOULDBLOCK and exists $self->{goners}{$pid}) {
|
||||
$child->{socket_err}++;
|
||||
$self->debug(
|
||||
"2: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ".(0+$!)."; OSErrno: ".(0+$^E)
|
||||
) if $self->{_debug} > 1;
|
||||
}
|
||||
else {
|
||||
$child->{socket_err}++;
|
||||
$self->debug(
|
||||
"2: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ".(0+$!)."; OSErrno: ".(0+$^E)
|
||||
) if $self->{_debug} > 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Process callbacks
|
||||
$self->debug("[$pid STDERR]: `$buff'\n") if $self->{_debug} > 1;
|
||||
if (defined $child->handler_stderr) {
|
||||
$child->handler_stderr->put(\$buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Call the "done" callback for anything that has exited and has no pending output
|
||||
my %not_pending = %{$self->{children}};
|
||||
for my $child (values %{$self->{children}}) {
|
||||
if ($child->{socket_err} >= 2) {
|
||||
delete $not_pending{$child->{pid}};
|
||||
}
|
||||
}
|
||||
for my $pid (keys %{$self->{goners}}) {
|
||||
my $child = $self->{children}{$pid} or next;
|
||||
if ($not_pending{$pid} and not $child->called_done) {
|
||||
$child->done_callback->($pid, $self->{goners}{$pid})
|
||||
if $child->done_callback;
|
||||
$child->called_done(1);
|
||||
}
|
||||
}
|
||||
|
||||
my $done;
|
||||
for my $child (values %{$self->{children}}) {
|
||||
if ($child->{socket_err} >= 2) {
|
||||
$done++;
|
||||
}
|
||||
}
|
||||
if ($done == keys %{$self->{children}} and (keys(%{$self->{children}}) <= keys(%{$self->{goners}}))) {
|
||||
# We still have children out there
|
||||
if (keys(%{$self->{children}}) > keys(%{$self->{goners}})) {
|
||||
$self->debug("We still have children") if $self->{_debug};
|
||||
return 1;
|
||||
}
|
||||
|
||||
$self->debug("Nothing else to do, flushing buffers")
|
||||
if $self->{_debug};
|
||||
|
||||
# Flush output filters
|
||||
for my $pid (keys %{$self->{children}}) {
|
||||
my $child = delete $self->{children}{$pid};
|
||||
$self->select->remove_stdout($pid);
|
||||
$self->select->remove_stderr($pid);
|
||||
if ($child->handler_stdout) {
|
||||
$child->handler_stdout->flush;
|
||||
}
|
||||
if ($child->handler_stderr) {
|
||||
$child->handler_stderr->flush;
|
||||
}
|
||||
}
|
||||
|
||||
# Nothing left to do
|
||||
$self->debug("Returning 0") if $self->{_debug};
|
||||
return 0;
|
||||
}
|
||||
|
||||
# for my $pid (@$stdout_pending) {
|
||||
# my $child = $self->{children}{$pid};
|
||||
# $self->debug("STDOUT pending for $pid") if $self->{_debug};
|
||||
#
|
||||
# my $ret = sysread($child->stdout_read, my $buff, READ_BLOCK);
|
||||
# if (!$ret) {
|
||||
# # Fun stuff with win32
|
||||
# if ($! != EAGAIN and $! != WSAEWOULDBLOCK) {
|
||||
# # Socket error
|
||||
# $self->{select}->remove_stdout($pid);
|
||||
# $self->debug("1: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ", (0+$!), "; OSErrno: ", (0+$^E))
|
||||
# if $self->{_debug};
|
||||
# }
|
||||
# else {
|
||||
# $self->debug("2: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ", (0+$!), "; OSErrno: ", (0+$^E))
|
||||
# if $self->{_debug};
|
||||
# }
|
||||
# }
|
||||
# else {
|
||||
# # Process callbacks
|
||||
# $self->debug("[$pid STDOUT]: `$buff'\n") if $self->{_debug} > 1;
|
||||
# if (defined $child->handler_stdout) {
|
||||
# $child->handler_stdout->put(\$buff);
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# for my $pid (@$stderr_pending) {
|
||||
# my $child = $self->{children}{$pid};
|
||||
# $self->debug("STDERR pending for $pid") if $self->{_debug};
|
||||
#
|
||||
# my $ret = sysread($child->stderr_read, my $buff, READ_BLOCK);
|
||||
# if (!$ret) {
|
||||
# # Fun stuff with win32
|
||||
# if ($! != EAGAIN and $! != WSAEWOULDBLOCK) {
|
||||
# # Socket error
|
||||
# $self->{select}->remove_stderr($pid);
|
||||
# $self->debug("1: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ", (0+$!), "; OSErrno: ", (0+$^E))
|
||||
# if $self->{_debug};
|
||||
# }
|
||||
# else {
|
||||
# $self->debug("2: $pid: Socket Read: Error: $!; OSError: $^E; Errno: ", (0+$!), "; OSErrno: ", (0+$^E))
|
||||
# if $self->{_debug};
|
||||
# }
|
||||
# }
|
||||
# else {
|
||||
# # Process callbacks
|
||||
# $self->debug("[$pid STDERR]: `$buff'\n") if $self->{_debug} > 1;
|
||||
# if (defined $child->handler_stderr) {
|
||||
# $child->handler_stderr->put(\$buff);
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $warned;
|
||||
sub check_for_exit {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
# This process was created with Win32::Process. The problem is
|
||||
# there is no way to reliably get the output from a Win32::Process
|
||||
# program in a loop like this. Output handles are not flushed when
|
||||
# process exits, which means that if it blocks a little we will
|
||||
# likly lose the last output it produces, this is so not nice.
|
||||
for my $pid (keys %{$self->{children}}) {
|
||||
my $child = $self->{children}{$pid};
|
||||
next if exists $self->{goners}{$pid};
|
||||
|
||||
if ($child->forked) {
|
||||
# Check if the program exited
|
||||
my $got_pid;
|
||||
my $waited = waitpid($pid, WNOHANG);
|
||||
my $killed = 1;
|
||||
$self->debug("waited: $waited; pid: $pid")
|
||||
if $self->{_debug};
|
||||
if ($waited < -1) {
|
||||
$self->{goners}{$pid} = $?;
|
||||
$child->exit_callback->($pid, $?)
|
||||
if $child->exit_callback;
|
||||
$self->debug(
|
||||
"forked child $pid exited with exit status (".
|
||||
($self->{goners}{$pid} >> 8).
|
||||
")\n"
|
||||
) if $self->{_debug};
|
||||
}
|
||||
elsif ($waited == -1) {
|
||||
$self->{goners}{$pid} = 0;
|
||||
$child->exit_callback->($pid, 0)
|
||||
if $child->exit_callback;
|
||||
}
|
||||
# elsif ($waited == -1) {
|
||||
# for my $pid (keys %{$self->{children}}) {
|
||||
# $self->{select}->remove_stdout($pid);
|
||||
# $self->{select}->remove_stderr($pid);
|
||||
# unless (exists $self->{goners}{$pid}) {
|
||||
# $self->{goners}{$pid} = -1;
|
||||
# $self->{children}{$pid}{exit_callback}->($pid, -1)
|
||||
# if $self->{children}{$pid}{exit_callback};
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# elsif (!$killed) {
|
||||
# $self->{goners}{$pid} = -1;
|
||||
# $self->{children}{$pid}{exit_callback}->($pid, -1)
|
||||
# if $self->{children}{$pid}{exit_callback};
|
||||
# $self->debug( "Could not get exit status of $pid")
|
||||
# if $self->{_debug};
|
||||
# }
|
||||
}
|
||||
else {
|
||||
|
||||
$self->debug("Checking if $pid is running") if $self->{_debug};
|
||||
if ($child->process and $child->process->Wait(0)) {
|
||||
$self->{goners}{$pid} = '';
|
||||
my $exit_code;
|
||||
$child->process->GetExitCode($exit_code);
|
||||
$self->{goners}{$pid} = $exit_code << 8;
|
||||
$child->exit_callback->($pid, ($exit_code << 8))
|
||||
if $child->exit_callback;
|
||||
$self->debug("$pid exited with status: $self->{goners}{$pid}")
|
||||
if $self->{_debug};
|
||||
}
|
||||
elsif ($self->{_debug}) {
|
||||
$self->debug("$pid is still running");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub oneway {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
$self->SUPER::oneway('inet');
|
||||
}
|
||||
|
||||
sub twoway {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self) = @_;
|
||||
$self->SUPER::twoway('inet');
|
||||
}
|
||||
|
||||
sub stop_blocking {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $socket_handle) = @_;
|
||||
my $set_it = "1";
|
||||
|
||||
# 126 is FIONBIO (some docs say 0x7F << 16)
|
||||
ioctl( $socket_handle,
|
||||
0x80000000 | (4 << 16) | (ord('f') << 8) | 126,
|
||||
$set_it
|
||||
) or die "ioctl: $^E";
|
||||
}
|
||||
|
||||
sub start_blocking {
|
||||
# ------------------------------------------------------------------------
|
||||
my ($self, $socket_handle) = @_;
|
||||
my $unset_it = "0";
|
||||
|
||||
# 126 is FIONBIO (some docs say 0x7F << 16)
|
||||
ioctl( $socket_handle,
|
||||
0x80000000 | (4 << 16) | (ord('f') << 8) | 126,
|
||||
$unset_it
|
||||
) or die "ioctl: $^E";
|
||||
}
|
||||
|
||||
1;
|
||||
|
Reference in New Issue
Block a user