A fix here, though it seems to perform too much dup() + dup2() work. It
can possibly be made more efficient later.
--
Paul Evans
=== modified file 'lib/IO/Async/ChildManager.pm'
--- lib/IO/Async/ChildManager.pm 2012-03-12 19:01:27 +0000
+++ lib/IO/Async/ChildManager.pm 2012-03-21 11:54:15 +0000
@@ -17,7 +17,7 @@
use Carp;
use Scalar::Util qw( weaken );
-use POSIX qw( _exit sysconf _SC_OPEN_MAX dup2 nice );
+use POSIX qw( _exit sysconf _SC_OPEN_MAX dup dup2 nice );
use constant LENGTH_OF_I => length( pack( "I", 0 ) );
@@ -563,6 +563,9 @@
# Count of how many times we'll need to use the current handles.
my %fds_refcount = %fd_in_use;
+ # To dup2() without clashes we might need to temporarily move some handles
+ my %dup_from;
+
my $max_fd = 0;
my $writepipe_clashes = 0;
@@ -591,6 +594,8 @@
# Keep a count of how many times it will be dup'ed from so we
# can close it once we've finished
$fds_refcount{$fileno}++;
+
+ $dup_from{$fileno} = $fileno;
};
$operation eq "keep" and do {
@@ -625,13 +630,20 @@
my $from = fileno $params[0];
if( $from != $fd ) {
+ if( exists $dup_from{$fd} ) {
+ defined( $dup_from{$fd} = dup( $fd ) ) or die "Cannot dup($fd) - $!";
+ }
+
+ my $real_from = $dup_from{$from};
+
POSIX::close( $fd );
- dup2( $from, $fd ) or die "Cannot dup2($from to $fd) - $!\n";
+ dup2( $real_from, $fd ) or die "Cannot dup2($real_from to $fd) - $!\n";
}
$fds_refcount{$from}--;
if( !$fds_refcount{$from} and !$fd_in_use{$from} ) {
POSIX::close( $from );
+ delete $dup_from{$from};
}
};
=== modified file 't/32loop-spawnchild-setup.t'
--- t/32loop-spawnchild-setup.t 2011-07-26 00:19:12 +0000
+++ t/32loop-spawnchild-setup.t 2012-03-21 11:54:15 +0000
@@ -4,7 +4,7 @@
use IO::Async::Test;
-use Test::More tests => 112;
+use Test::More tests => 116;
use Test::Fatal;
use File::Temp qw( tmpnam );
@@ -308,6 +308,36 @@
is( $buffer, "HELLO\n", '$buffer after pipe quad to fd0/fd1' );
}
+{
+ # Try to swap two filehandles and cause a dup2() collision
+ my @fhA = $loop->pipepair or die "Cannot pipepair - $!";
+ my @fhB = $loop->pipepair or die "Cannot pipepair - $!";
+
+ my $filenoA = $fhA[1]->fileno;
+ my $filenoB = $fhB[1]->fileno;
+
+ TEST "fd swap",
+ setup => [
+ "fd$filenoA" => $fhB[1],
+ "fd$filenoB" => $fhA[1],
+ ],
+ code => sub {
+ $fhA[1]->print( "FHA" ); $fhA[1]->autoflush(1);
+ $fhB[1]->print( "FHB" ); $fhB[1]->autoflush(1);
+ return 0;
+ },
+
+ exitstatus => 0;
+
+ my $buffer;
+
+ read_timeout( $fhA[0], $buffer, 3, 0.1 );
+ is( $buffer, "FHB", '$buffer [A] after dup2() swap' );
+
+ read_timeout( $fhB[0], $buffer, 3, 0.1 );
+ is( $buffer, "FHA", '$buffer [B] after dup2() swap' );
+}
+
TEST "stdout close",
setup => [ stdout => [ 'close' ] ],
code => sub { print "test"; },