coreolyn has asked for the wisdom of the Perl Monks concerning the following question:
This sub is at the core of controlling / monitoring all external functionality my Perl scripts will be forced to utilize. It is imperative that I trap and log STDERR which is why I went with Open3. However, Niether of the Camels read clearly to me on bidirectional communication, and just looking at this code I get the feeling I'm setting myself up for headaches long term. It's intended to be handed off to others instead of having to support it myself.
In particular I'm concerned with my assumptions about gathering $stdout and $stderr, and definately have my ears open to all input
# execute() runs a program on any platform and traps
# the STDOUT and STDERR. A Calling program must handle
# the return value to determine if it worked or not.
# if there is a $@ (trapped error) there are BIG problems
# There is a danger in overruning the Perl buffers
# (if they exceed 255 chars) with either the commandline writer
# or the output buffer reader this function gets around this by
# (when necessary) creating a batch file in a temporary directory
# and then executing that file.
sub execute {
my $execute = $_[0];
my $logfile = $_[1];
my $writer = IO::Handle->new();
my $reader = IO::Handle->new();
my $debug = "false";
#$debug = "true";
if ( $debug =~ /true/i && $logfile ) {
print "::Utility::execute::\$execute = $execute\n";
} elsif ( $debug =~ /true/i ) {
print "::Utility::execute::\$logfile = $logfile\n";
}
local (*FILE);
if ( (length $execute) > 254 ) {
my $workdir = OSworkdir();
OSmkdir($workdir);
my $filename;
if ( $^O =~ /win/i ) {
$filename = "$workdir"."/execute.bat";
$filename = OSpath($filename);
open ( FILE, ">$filename" ) or OSlogger( $logfile, "::Util
+ity::execute Can't open $filename", 1) and die "aw hell";
} elsif ( $^O =~ /solaris/i ) {
$filename = OSpath("$workdir/execute.sh");
open ( FILE, 774, ">$filename" ) or OSlogger( $logfile, ":
+:Utility::execute Can't open $filename", 1) and die "aw hell";
}
print FILE $execute;
close (FILE);
OSlogger( $logfile, "Created executable $filename", 1);
$execute = $filename;
}
if ( $logfile ) {
OSlogger( $logfile, "Executing $execute", 1);
}
# Here's the actual execution.
my $pid;
eval { $pid = open3( $writer, $reader, $reader, $execute ); };
die "::Utility::execute died a horrible death!\n" if $@;
close($writer);
# Lets get STDOUT and STDERR ( note the first read always(?) seems
+ to get nothing)
my $stdout = <$reader> ;
$stdout = <$reader> ;
my $return_val = $stdout;
my @stderr;
while (<$reader>) {
push (@stderr, $_ );
}
if ( $logfile ) {
OSlogger( $logfile,"\$run RETURNED: $stdout\n", 1);
}
if ( @stderr ) {
OSlogger( $logfile,"Error on $execute\n\n @stderr\n", 1);
$return_val = @stderr;
}
# Return the output
return $return_val;
}
coreolyn
Edit Masem 2002-02-19 - Added READMORE tag after 1st para
Re: Open3 and bad gut feeling
by particle (Vicar) on Feb 17, 2002 at 00:20 UTC
|
i've used IPC::Open3 because i needed to capture STDIN, STDERR, return code, and pid at once, and it's the only way to do it. you can find a summary on my implementation at once again: program output and return code
this code is running on a production system that synchronizes code between PVCS repositories. it's been running for over a year, and there's only ever been one problem: sporadic failures with IO::Pipe. see my question here: intermittent problem with IPC::Open3. finding no other resolution, i used a short sleep command.
perhaps you'll get some mileage from this.
~Particle
| [reply] |
|
| [reply] |
|
sleep(1); ought to do it. it's quite sporatic (~1 per 1000 calls) and frustrating.
the production server is a 180MHz pentiumpro w/256MB RAM, running NT 4 SP6, but it has ultra-fast-wide scsi, and a fat network pipe. i did not see this problem on my (then) desktop, a dual pIII 450. your mileage may vary.
good luck!
~Particle
| [reply] [d/l] |
Re (tilly) 1: Open3 and bad gut feeling
by tilly (Archbishop) on Feb 17, 2002 at 00:55 UTC
|
A sneaking feeling tells me that you will shortly be
Suffering
from Buffering. (There is a similar warning in
IPC::Open3.) Namely if there is, for instance, a long
error statement, you may be trying to read from its STDOUT
while it is waiting for you to check its STDERR. This
might take a while...
You are likely to need the select command or its OO
wrapper IO::Select to do this properly. (I think you
will find IO::Select easier to figure out.) | [reply] |
Re: Open3 and bad gut feeling
by coreolyn (Parson) on Feb 16, 2002 at 23:04 UTC
|
I should mention that CPAN mods are not a viable option around here (That doesn't reflect my policy just my employers).
coreolyn
| [reply] |
|
| [reply] [d/l] |
|
While policy does not explicitly forbid CPAN modules, they are completely unsupported inhouse. Without inhouse support there's too many hurdles to overcome to use CPAN modules effectively. It's more like enforcing a ban by attrition than an outright ban.
I'm working on 'em though projects like the one I'm working on currently will go a long way to insinuate Perl a little deeper into the corporate culture/infra-structure
For now that has to be the best next step. Once they are
comfortable with that then progress on CPAN can be made. I know that might sound a bit backword to outsiders but I've been advocating actively for a few years here. Patience and solid code in the clutch is the best course for now :)
coreolyn
| [reply] |
Re: Open3 and bad gut feeling
by particle (Vicar) on Feb 20, 2002 at 08:04 UTC
|
coreolyn, i've been following this node very closely. you can see by my replies that i have been trying to solve a similar issue. i was never satisfied with my implementation... mostly due to tilly's constant, nagging reminders to use select :)
well, i did a little investigation on select, and i found some interesting things you'll appreciate. for instance, select is only implemented on sockets in Win32. hrmm. also, open2/open3 cannot redirect input/output in a child process via fork in Win32. i'm afraid a lot of the code you have there is not doing anything useful.
here's a list of links for more information:
perlport
IO::Select on Windows
stdin-socket without fork()??
here's a test script to verify select is in fact, not working. can_write, can_read, and has_exception all come back undef on Win32... (see below)
#!/usr/bin/perl
use IO::Select;
# create typeglobs and add to selector
$output_select = IO::Select->new( *CMD_ERR, *CMD_OUT, *CMD_IN );
# test selector methods
$cnt = $output_select->count();
print "count: $cnt\n";
@bob = $output_select->handles();
print "list: @bob\n";
@bob = $output_select->can_write(10);
print "write: @bob\n";
@bob = $output_select->can_read(10);
print "read: @bob\n";
@bob = $output_select->has_exception(10);
print "exception: @bob\n";
close(CMD_IN);
close(CMD_OUT);
close(CMD_ERR);
i'm now working on a client / server solution for executing external programs and redirecting output using sockets. as soon as i have something solid, i'll be sure to post it, and /msg you with the node_id.
thank you for using microsoft ;-D
~Particle | [reply] [d/l] |
|
Hi particle
I have been working on a similar problem Win32 capturing output from a process that may hang, capturing stdout and stderr from a child proc, must work under windows and unix and there is a good chance the child will hang and have to be killed.
I have a partial solution at Re^4: Win32 capturing output from a process that may hang using threads and non blocking thread queues. This does not however do stderr properly. I have started trying to use open3 for this but have found it a can of worms. Did you get anywhere with the client server socket talking approach ?
Cheers, R.
Pereant, qui ante nos nostra dixerunt!
| [reply] |
|
actually, i later found IPC::Run, which does a great job for me, even though it says win32 support is *experimental*. in fact, i'm using happily using it right now to glue two apps together :)
| [reply] |
Re: Open3 and bad gut feeling : 357 char STDIN limit?
by coreolyn (Parson) on Feb 19, 2002 at 15:57 UTC
|
Arg..
I had thought I had overcome the problem of executing large command lines by writing the command line into script that is executed. However, even though the created script runs fine directly from the command line, I have found that if I run it through the attached sub and the line to be executed execeeds 357 characters I 'hang' (MSwin32 - 2000). Additionally I have attempted to implement IO::Handle and IO::Select but apparently not in an effective way. (If it is indeed some type of a buffering problem). I lost all day yesterday to this code and hope someone can pinpoint my ignorance, or am I just exceeding the limitations of open3?
Here's the relevant code. (note: I pushed all the logging to a separate module)
# RETURNS: %OSexec = ( stdout => $stdout, stderr => $stderr pid => $pi
+d )
sub OSexecute {
my $execute = $_[0];
my $stdin = IO::Select->new();
# not sure what difference <&STDIN and \*STDIN have
$stdin->add("\*STDIN");
my $din = IO::Handle->new();
$stdin->add($din);
my $stdout = IO::Select->new();
$stdout->add(\*STDOUT);
my $dout = IO::Handle->new();
$stdout->add($dout);
my $stderr = IO::Select->new();
$stderr->add(\*STDERR);
my $derr = IO::Handle->new();
$stderr->add($derr);
my ( $pid, $val, $out, $err );
my ( @stdin, @stdout, @stderr );
my $debug = "false";
#$debug = "true";
if ( $debug =~ /true/i ) {
print "OSify::Execute::OSexecute::\$execute = $execute\n";
}
# Here is the actual execution
eval {
print "Executing $execute\n";
$pid = open3( $din, $dout, $derr, $execute );
print "Waiting for pidout\n";
# waitpid waits for the proces to exit
# $val could be used as a means to determine status
# while waiting if that functionality becomes needed.
$val = waitpid(-1,0); #wait's for process to complete
# Process the results
# Standard Out
my $line;
my @stdout = <$dout>;
foreach $line (@stdout) {
chomp($line);
$out = $out . $line;
}
if ( ! $out ) {
$out = 1;
}
# Standard Error
@stderr = <$derr>;
foreach $line ( @stderr ) {
chomp($line);
$err = $err . $line;
}
if ( ! $err ) {
$err = 1;
}
};
$@ && die "OSify::OSexecute died upon execution of\n$execute\nWith
+$@";
# Question remains what activity qualifies as draining the buffer?
$din->flush();
$din->close;
$dout->flush();
$dout->close;
$derr->flush();
$derr->close;
my %OSexec = (
stdout => $out,
stderr => $err,
pid => $pid,
);
print "Execute Finished with @{[%OSexec]}\n";
return %OSexec;
}
Here's a very typical script it will execute. The antcall.bat doesn't even have to exist to duplicate the
problem.
E:\\cccharv\\JDK\\Release\\scripts\\antcall.bat -logfile E:\\cccharv\\
+testApp\\testVer\\Unit_Test\\antscripts\\build\\logs\\20020219-082532
+_Ant_build_testApp.txt -buildfile E:\\cccharv\\testApp\\testVer\\Unit
+_Test\\antscripts\\buildTESTAPP.xml compileTESTAPPClean buildTESTAPPJ
+ar buildTESTAPPWar buildClientControllerEJB buildTESTAPPSessionEJB bu
+ildTESTAPPEar
Getting rid of the last three characters eliminates the hang
coreolyn | [reply] [d/l] [select] |
|
Well I've narrowed the problem to STDOUT. If I turn @echo off I've eliminated the problem. Which for this particular app is ok, but I'd sure like to see the problem eliminated completely and to get a better handle on filehandles/buffering.
Update:Aw frick'n frack'n bull riffin DOS frmbln grrr..
@echo off just fixed the Perl problem now the OS can't handle it... This is why I quit developing on DOS!!!!
coreolyn ... mumble mumble mumble.
| [reply] |
|
Welp.. turns out that if I pass the command+args as an array instead of a string to Open3 the problem disapears. I don't even have to create an script to run large command lines.
The DOS problem was eliminated by slurping up the args via set VAR=%*.
So to put an end to this here's the code as I am going to run with it.
# RETURNS: %OSexec = ( stdout => $stdout, stderr => $stderr pid => $pi
+d )
sub OSexecute {
my @execute = @_;
my $stdin = IO::Select->new();
# not sure what difference <&STDIN and \*STDIN have
$stdin->add("\*STDIN");
my $din = IO::Handle->new();
$din->autoflush(1);
$stdin->add($din);
my $stdout = IO::Select->new();
$stdout->add(\*STDOUT);
my $dout = IO::Handle->new();
$dout->autoflush(1);
$stdout->add($dout);
my $stderr = IO::Select->new();
$stderr->add(\*STDERR);
my $derr = IO::Handle->new();
$derr->autoflush(1);
$stderr->add($derr);
my ( $pid, $val);
my $debug = "false";
#$debug = "true";
if ( $debug =~ /true/i ) {
print "OSify::Execute::OSexecute::\$execute = @execute\n";
}
# Here is the actual execution
eval {
$pid = open3( $din, $dout, $derr, @execute );
# waitpid waits for the proces to exit
# $val could be used as a means to determine status
# while waiting if that functionality becomes needed.
$val = waitpid(-1,0); #wait's for process to complete
};
$@ && die "OSify::OSexecute died upon execution of\n@execute\nWith
+$@";
# Gather the results
my $line;
# Standard Out
my @stdout = <$dout>;
my $out;
foreach $line (@stdout) {
$line = OSify::Utility::trimSpaces($line);
$out = $out . $line;
}
if ( ! $out ) {
$out = 1;
}
# Standard Error
my @stderr = <$derr>;
my $err;
foreach $line ( @stderr ) {
$line = OSify::Utility::trimSpaces($line);
$err = $err . $line;
}
if ( ! $err ) {
$err = 1;
}
# Flush and close the Filehandles
$din->flush();
$din->close;
$dout->flush();
$dout->close;
$derr->flush();
$derr->close;
my %OSexec = (
stdout => $out,
stderr => $err,
pid => $pid,
);
return %OSexec;
}
coreolyn | [reply] [d/l] [select] |
|
# RETURNS: ( $stdout, $stderr ) Both are refs to an array of lines.
sub OSexecute {
my @execute = @_;
local $, = ' '; # for print "...@execute..."
my $debug = 0;
#$debug = 1;
print("OSexecute(@execute)\n") if ($debug);
# Here is the actual execution.
my $pid = eval { open3($din, $dout, $derr, @execute) };
die "OSexecute(@execute): $@" if ($@);
# Wait for process to complete.
waitpid($pid, 0);
# Gather the results
my @stdout = <$dout>;
my @stderr = <$derr>;
# We should check the return code of the child.
# Gotta trap SIGPIPE for that.
return ( \@stdout, \@stderr );
}
There's a big problem
{
my $file_name;
foreach $file_name ('c:\\tinyfile.txt', 'c:\\biggfile.txt') {
my ($stdout, $stderr);
print("$file_name\n", ("=" x length($file_name))."\n", "\n");
($stdout, $stderr)
= OSexecute('cmd.exe', '/c', 'type', "\"$file_name\"");
print("stdout\n", "------\n", @$stdout);
print("Nothing was sent to STDOUT.\n") unless (@$stdout);
print("\n");
print("stderr\n", "------\n", @$stderr);
print("Nothing was sent to STDERR.\n") unless (@$stderr);
print("\n", "\n");
}
}
outputs
c:\tinyfile.txt
===============
stdout
------
foo
bar
bla
stderr
------
Nothing was sent to STDERR.
c:\biggfile.txt
===============
*** HANGS ***
The problem is that file handles (including $dout and $derr) have a buffer that's limited in size. biggfile.txt is less than 2KB, which is really quite small, so this needs to be fixed.
Fix
# RETURNS: ( $stdout, $stderr ) Both are refs to an array of lines.
sub OSexecute {
my @execute = @_;
my @stdout;
my @stderr;
local $, = ' '; # for print "...@execute..."
my $debug = 0;
#$debug = 1;
print("OSexecute(@execute)\n") if ($debug);
# Here is the actual execution.
my $pid = eval { open3($din, $dout, $derr, @execute) };
die "OSexecute(@execute): $@" if ($@);
my $select = IO::Select->new();
$select->add($dout);
$select->add($derr);
my @ready;
my $fh;
# Gather the results
while (@ready = $select->can_read()) {
foreach $fh (@ready) {
push(@stdout, <$fh>) if ($fh == $dout);
push(@stderr, <$fh>) if ($fh == $derr);
}
}
# Wait for process to complete and reap it.
waitpid($pid, 0);
# We should check the return code of the child.
# Gotta trap SIGPIPE for that.
return ( \@stdout, \@stderr );
}
Fixed?
Oops! select() (and IO::Select) doesn't work in Windows. That sucks. It means we're gonna have to use threads!!! I have no experience with threads, so maybe another day.
| [reply] [d/l] [select] |
|
|
|