I am hoping there is a someone brilliant on this list that can help me. A
little while ago, I posted to clp.perl asking how I can capture the trace
output from DBI into a variable. Since DBI is an external process, I
couldn't do it just by piping STDERR. Benjamin Goldberg came up with a
module called IO::Capture (see below). It works amazingly well in standard
Perl. Here is a sample script:
use IO::Capture;
$capturer = IO::Capture->new(\*STDERR);
use DBI;
$dbh = DBI->connect ("DBI:mysql:test:localhost", "username", "password", {
RaiseError => 0, PrintError => 0 });
DBI->trace( 1 );
$sth = $dbh->prepare (qq{
CREATE TABLE IF NOT EXISTS test_table
(
a CHAR(15) NOT NULL,
b INT UNSIGNED NOT NULL
)
});
$sth->execute ();
$sth->finish ();
$dbh->disconnect ();
$text = $capturer->capture;
print qq{
Output is:
$text
};
The problem is that once I try this script through mod_perl, it hangs the
child process infinitely. And then on subsequent requests, it has to create
a new process. This eventually results in the whole machine spiraling down
because hundreds of Apache children are hung. What I am hoping is that
someone will spot something that neither Ben nor I have been able to spot.
The module appears after my name.
Thanks for any help you can provide.
Mark
package IO::Capture;
use strict;
use warnings;
use Symbol qw(gensym);
sub new {
(my ($class, $filehandle) = @_) == 2
or croak("Usage: IO::Capture->new(\$filehandle)");
if( ref $filehandle or ref \$filehandle eq "GLOB" ) {
$filehandle = \*$filehandle; # this is a sort of typecast.
} else {
$filehandle = caller() . "::" . $filehandle
unless $filehandle =~ /::/ or
$filehandle =~ /^STD(?:IN|OUT|ERR)\z/;
no strict 'refs';
$filehandle = \*$filehandle;
}
defined(fileno $filehandle)
or croak("Argument to IO::Capture->new has no fileno()");
my $save = gensym;
open $save, ">&".fileno($filehandle)
or die sprintf("Couldn't dup2(%s,%s): $!\n",
fileno($save),fileno($filehandle));
my ($getresponse, $sendresponse) = (gensym, gensym);
pipe($getresponse, $sendresponse) or die "pipe: $!";
my ($readnew, $writenew) = (gensym, gensym);
pipe($readnew, $writenew) or die "pipe: $!";
open( $filehandle, ">&" . fileno($writenew) )
or die sprintf("Couldn't dup2(%s,%s): $!\n",
fileno($filehandle),fileno($writenew));
close($writenew);
defined( my $pid = fork ) or do {
my $err = $!;
unless( open $filehandle, ">&".fileno $save ) {
my $err2 = $!;
open STDERR, $^O =~ /win/i ? ">con" : ">/dev/tty"
if $filehandle == \*STDERR;
die "fork: $err, dup2: $err2";
}
die "fork: $err";
};
# readnew, writenew, and sendresponse are automatically closed
# when we return here in the parent because they go out of scope,
# resulting in their their refcounts going to 0.
return bless [$filehandle, $save, $getresponse, $pid], $class
if $pid;
close($getresponse); # not used, so close it.
close($writenew); # MUST close this, or deadlock will occur!
# MUST close or re-open $filehandle, or deadlock will occur!
$filehandle == \*STDERR and (
open STDERR, ">&".fileno $save or
open STDERR, $^O =~ /win/i ? ">con" : ">/dev/tty"
) or close $filehandle;
close $save; # not used from here on, so close it.
my ($got, $n) = "";
1 while $n = sysread $readnew, $got, 4096, length $got;
die "sysread: $!" unless defined $n;
print $sendresponse $got or die "print: $!";
exit;
}
sub capture {
my $self = shift;
my ($fh, $saved, $get, $pid) = splice @$self, 0;
unless( open $fh, ">&" . fileno $saved ) {
open $fh, $^O =~ /win/i ? ">con" : ">/dev/tty"
if $fh == \*STDERR;
die "Couldn't restore filehandle: $!";
} else { close $saved }
my ($got, $n) = "";
while($n = sysread $get, $got, 4096, length $got) {}
defined($n) or die "sysread: $!";
if( waitpid $pid, 0 ) {
warn sprintf "Child exited with code 0x%04X", $? if $?;
} else { warn "waitpid: $!" } return $got;
}
# like using autouse.pm but even more lightweight.
sub croak {
undef &croak;
require Carp;
*croak = \&Carp::croak;
goto &croak;
}
1;
__END__
perl -MIO::Capture
$x = IO::Capture->new(\*STDERR);
print "now capturing\n";
warn qq[captured ok\n];
print "captured text 'captured ok' shouldn't have appeard\n";
$y = $x->capture;
print "capture didn't block\n";
print $y;
warn "Restored ok\n";
__END__
now capturing
captured text 'captured ok' shouldn't have appeard
capture didn't block
captured ok
Restored ok
perl -MIO::Capture
$x = IO::Capture->new(\*STDERR);
print "now capturing\n";
system(q[perl -e "print STDERR qq[captured ok\n]"]);
print "captured text 'captured ok' shouldn't have appeard\n";
$y = $x->capture;
print "capture didn't block\n";
print $y;
warn "Restored ok\n";
__END__
now capturing
captured text 'captured ok' shouldn't have appeard
capture didn't block
captured ok
Restored ok