Skip Menu |

This queue is for tickets about the IO-Async CPAN distribution.

Report information
The Basics
Id: 75925
Status: resolved
Priority: 0/
Queue: IO-Async

People
Owner: Nobody in particular
Requestors: leonerd-cpan [...] leonerd.org.uk
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: 0.46
Fixed in:
  • 0.46_002
  • 0.47



Subject: ChildManager fd* setup operations can't handle complex reassignments
The simple dup2() logic cannot handle things like collisions of file descriptors, or moving some out of the way for others. New unit-test case: # 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' ); -- Paul Evans
A fix here, though it seems to perform too much dup() + dup2() work. It can possibly be made more efficient later. -- Paul Evans
Subject: rt75925.patch
=== 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"; },