Skip Menu |

This queue is for tickets about the POE CPAN distribution.

Report information
The Basics
Id: 28617
Status: rejected
Priority: 0/
Queue: POE

People
Owner: Nobody in particular
Requestors: MSHIBLA [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in:
  • 0.38
  • 0.9989
Fixed in: (no value)



Subject: POE::Wheel::Run not deleting stdout file handle on child exit
I am seeing a problem in POE::Wheel::Run for at least POE versions 0.9989 and 0.38. When using POE::Wheel::Run to spawn an external command, POE is properly setting up two pipes (using the default 'pipe' Conduit via POE::Pipe::TwoWay), one for stdout and one for stderr. It seems that when the child process terminates, the stderr pipe is selected, it is removed from the POE kernel, and the file handle is deleted. The stdout pipe is selected, it is removed from the POE kernel, but the file handle is never deleted. This causes the wheel to hang around forever (as well as the enclosing session). In a long-running application, this behavior eventually eats up all the available file descriptors. I wrote a test program to illustrate. It simply invokes [ '/bin/echo', 'hi' ]. When run "normally", the problem is illustrated. I can "solve" the problem by calling $kernel->_data_handle_clear_session($session) from the CloseEvent handler of the wheel. When that internal method is used, (in the below trace) fileno (6) is removed, removed again, and deleted (and then a warning is generated that fileno (6) is not registered with POE::Kernel). The session is then removed and the program terminates properly. See the attached poe_leak.pl for the demonstration code. Set $CLEAN_HANDLES appropriately (boolean). Here is the relevant trace: <fh> adding fd (6) in mode (1) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 324 <rc> incrementing refcount for session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Sessions.pm line 358 <fh> pause test: fileno(6) mode(1) count(0) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 706 <fh> adding fd (7) in mode (0) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 324 <rc> incrementing refcount for session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Sessions.pm line 358 ... <rc> +----- GC test for session 2 (POE::Session=ARRAY(0x8559a90)) (POE::Session=ARRAY(0x8559a90)) ----- <rc> | total refcnt : 7 <rc> | event count : 2 <rc> | post count : 2 <rc> | child sessions: 0 <rc> | handles in use: 2 <rc> | aliases in use: 0 <rc> | extra refs : 0 <rc> | pid count : 1 <rc> +--------------------------------------------------- ... <fh> decrementing event count in mode (0) for fileno (7) from count (1) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 195 <ev> dispatching event 7 ``POE::Wheel::Run(1) -> select stderr'' to session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Kernel.pm line 972 2 -> POE::Wheel::Run(1) -> select stderr (from //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm at 254) 2 -> error_event (from //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Wheel/Run.pm at 776) <fh> removing handle (GLOB(0x84c52d8)) fileno (7) mode (0) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 528 <fh> deleting handle (GLOB(0x84c52d8)) fileno (7) entirely at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 609 <rc> decrementing refcount for session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Sessions.pm line 331 <ev> event 7 ``POE::Wheel::Run(1) -> select stderr'' returns (undef) <rc> +----- GC test for session 2 (POE::Session=ARRAY(0x8559a90)) (POE::Session=ARRAY(0x8559a90)) ----- <rc> | total refcnt : 4 <rc> | event count : 1 <rc> | post count : 1 <rc> | child sessions: 0 <rc> | handles in use: 1 <rc> | aliases in use: 0 <rc> | extra refs : 0 <rc> | pid count : 1 <rc> +--------------------------------------------------- ... <fh> decrementing event count in mode (0) for fileno (6) from count (1) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 195 <ev> dispatching event 8 ``POE::Wheel::Run(1) -> select stdout'' to session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Kernel.pm line 972 2 -> POE::Wheel::Run(1) -> select stdout (from //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm at 254) 2 -> error_event (from //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Wheel/Run.pm at 676) 2 -> task_done (from //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Wheel/Run.pm at 681) <rc> decrementing refcount for session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Sessions.pm line 331 <rc> decrementing refcount for session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Sessions.pm line 331 <fh> removing handle (GLOB(0x81577d4)) fileno (6) mode (0) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/FileHandles.pm line 528 <ev> event 8 ``POE::Wheel::Run(1) -> select stdout'' returns (undef) <rc> +----- GC test for session 2 (POE::Session=ARRAY(0x8559a90)) (POE::Session=ARRAY(0x8559a90)) ----- <rc> | total refcnt : 2 <rc> | event count : 0 <rc> | post count : 0 <rc> | child sessions: 0 <rc> | handles in use: 1 <rc> | aliases in use: 0 <rc> | extra refs : 0 <rc> | pid count : 1 <rc> +--------------------------------------------------- ... <sg> POE::Kernel detected SIGCHLD (pid=2711; exit=0) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Signals.pm line 493 <ev> enqueued event 9 ``sig_child'' from session piias1358-46b0a87000000a96 (POE::Kernel=ARRAY(0x8342170)) to session 2 (POE::Session=ARRAY(0x8559a90)) at 1185982577.61384 at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Events.pm line 73 ... <ev> Dispatching event 9 ``sig_child'' (CHLD 2711 0) from session piias1358-46b0a87000000a96 (POE::Kernel=ARRAY(0x8342170)) to session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Kernel.pm line 846 <ev> dispatching event 9 ``sig_child'' to session 2 (POE::Session=ARRAY(0x8559a90)) at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Kernel.pm line 972 2 -> sig_child (from //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Resource/Signals.pm at 506) <ev> event 9 ``sig_child'' returns (undef) ... <rc> +----- GC test for session 2 (POE::Session=ARRAY(0x8559a90)) (POE::Session=ARRAY(0x8559a90)) ----- <rc> | total refcnt : 1 <rc> | event count : 0 <rc> | post count : 0 <rc> | child sessions: 0 <rc> | handles in use: 1 <rc> | aliases in use: 0 <rc> | extra refs : 0 <rc> | pid count : 0 <rc> +--------------------------------------------------- ... <rc> ,----- Kernel Activity ----- <rc> | Events : 1 <rc> | Files : 1 <rc> | Extra : 0 <rc> | Procs : <rc> `--------------------------- <rc> ... at //ms/dist/perl5/PROJ/POE/0.9989/lib/perl5/POE/Kernel.pm line 596 <ev> Kernel::run() iterating. now(1.6166) timeout(28.9307) then(30.5473) <fh> ,----- SELECT BITS IN ----- <fh> | READ : 00000000 <fh> | WRITE : 00000000 <fh> | EXPEDITE: 00000000 <fh> `-------------------------- ...
Subject: poe_leak.pl
#!/ms/dist/perl5/bin/perl5.8 use strict; use warnings; use MSDW::Version 'POE' => '0.9989', # 'POE' => '0.38', ; use vars qw( $CLEAN_HANDLES $DELAY $TIMEOUT $VERBOSE ); sub POE::Kernel::TRACE_DEFAULT () { 1 } sub POE::Kernel::TRACE_DESTROY () { 1 } sub POE::Kernel::TRACE_EVENTS () { 1 } sub POE::Kernel::TRACE_FILES () { 1 } sub POE::Kernel::TRACE_PROFILE () { 1 } sub POE::Kernel::TRACE_REFCNT () { 1 } sub POE::Kernel::TRACE_RETVALS () { 1 } sub POE::Kernel::TRACE_SESSIONS () { 1 } sub POE::Kernel::TRACE_SIGNALS () { 1 } sub POE::Kernel::ASSERT_EVENTS () { 1 } sub POE::Kernel::ASSERT_FILES () { 1 } sub POE::Kernel::CATCH_EXCEPTIONS () { 1 } use POE qw( Wheel::Run Filter::Reference Filter::Line ); sub log_it { my $mesg = shift; my $retval = print STDOUT ($mesg); return($retval); }; { $CLEAN_HANDLES = 1; $DELAY = 5; $TIMEOUT = 30; $VERBOSE = 5; my $session = POE::Session->create( 'inline_states' => { '_start' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); ($VERBOSE > 2) && log_it( "$alias: do_system_session: started session: '$session_id'" . "\n" ); $kernel->yield('next_task'); }, '_stop' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); ($VERBOSE > 2) && log_it( "${alias}: do_system_session: stopped session: " . "'$session_id'\n" ); }, '_child' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); ($VERBOSE > 2) && log_it( "${alias}: do_system_session: sigchld session: " . "'$session_id'\n" ); }, '_parent' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); ($VERBOSE > 2) && log_it( "${alias}: do_system_session: child session '$session_id' " . "has new parent\n" ); }, '_default' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); ($VERBOSE > 2) && log_it( "${alias}: do_system_session: default caught an unhandled " . "'$_[ARG0]' event. These '$_[ARG1]' parameters were " . "sent. session: '$session_id'\n" ); }, 'sig_child' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); my $pid = $_[ARG1]; my $rc = $_[ARG2]; my $id = $heap->{'ids'}{$pid} || ""; ($VERBOSE > 3) && log_it( "sig_child(): session = $session_id, wheel = $id, entered " . "sig_child, pid = $pid, rc = $rc, alias = $alias\n" ); # child death signals are issued by the OS and sent to all # sessions; we want to handle only our own children if ($id) { ($POE::VERSION >= 0.20) && $kernel->sig_handled(); my %args = ('id' => $id, 'rc' => $rc); if ($heap->{'closed'}{$id}) { delete($heap->{'ids'}{$pid}); delete($heap->{'pids'}{$id}); } ($VERBOSE > 2) && log_it( "sig_child(): session = $session_id, wheel = $id, " . "pid = $pid, rc = $rc\n" ); } }, '_kill' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); ($VERBOSE > 2) && log_it( "${alias}: do_system_session: _kill timeout: '$TIMEOUT' " . "session: '$session_id'\n" ); ID: foreach my $id (keys(%{ $heap->{'dss_task'} })) { $heap->{'dss_task'}{$id}->kill(9); } }, 'next_task' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $session_id = $session->ID(); ($VERBOSE > 2) && log_it( "${alias}: do_system_session: next_task session: " . "'$session_id'\n" ); $heap->{'task'} = POE::Wheel::Run->new( 'Program' => [ '/bin/echo', 'hi' ], 'CloseEvent' => 'task_done', 'StdoutEvent' => 'task_result', 'StderrEvent' => 'task_debug', 'StdinEvent' => 'task_debug', 'ErrorEvent' => 'error_event', 'StdoutFilter' => POE::Filter::Line->new(), # 'Conduit' => 'socketpair', ); $heap->{'task'} || die("$0: can't POE::Wheel::Run->new"); my $pid = $heap->{'task'}->PID(); ($VERBOSE > 3) && log_it( "$alias: do_system_session: successfully started task " . "pid: '$pid' in session: '$session_id'\n" ); $kernel->delay_set('_kill', $TIMEOUT); $kernel->sig_child($pid, 'sig_child'); my $id = $heap->{'task'}->ID(); $heap->{'pids'}{$id} = $pid; $heap->{'ids'}{$pid} = $id; $heap->{'dss_task'}{$id} = $heap->{'task'}; $heap->{'pid_to_wheel'}{$pid} = $heap->{'task'}; $heap->{'id_to_wheel'}{$id} = $heap->{'task'}; }, 'task_result' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $txt = $_[ARG0]; my $wheel = $_[ARG1]; my $session_id = $session->ID(); log_it( "$alias: txt: '$txt', wheel: '$wheel', " . "session: '$session_id' stdout\n" ); }, 'task_done' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $wheel = $_[ARG0]; my $session_id = $session->ID(); $kernel->delay('_kill'); log_it( "$alias: wheel: '$wheel' session: '$session_id' has finished\n" ); my $wheel_run = $heap->{'task'}; $heap->{'closed'}{$wheel} = 1; $heap->{'task'} = 2; $wheel_run->kill(); ($CLEAN_HANDLES) && $kernel->_data_handle_clear_session($session); }, 'task_debug' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $op = $_[ARG0]; my $errno = $_[ARG1]; my $error = $_[ARG2]; my $wheel = $_[ARG3]; my $fh = $_[ARG4]; my $session_id = $session->ID(); ($VERBOSE > 3) && log_it( "$alias: error op: '$op', errno: '$errno', error: " . "'error', wheel: '$wheel', fh: '$fh' stderr " . "session: '$session_id'\n" ); }, 'error_event' => sub { my $kernel = $_[KERNEL]; my $heap = $_[HEAP]; my $session = $_[SESSION]; my $alias = $heap->{'alias'}; my $op = $_[ARG0]; my $errno = $_[ARG1]; my $error = $_[ARG2]; my $wheel = $_[ARG3]; my $fh = $_[ARG4]; my $session_id = $session->ID(); if ( ($op eq 'read') && ($errno eq '0') && ($error eq '') ) { # POE network components seem to emit read "errors" at shutdown } else { log_it( "$alias: error op: '$op', errno: '$errno', error: '$error', " . "wheel: '$wheel', fh: '$fh', session: '$session_id'\n" ); $heap->{'txt'}{'out'} = [ "failed: $alias: error op: '$op', errno: '$errno', error: " . "'$error', wheel: '$wheel', fh: '$fh' error event " . "session: '$session_id'" ]; } }, }, 'heap' => { 'alias' => "do_system_session", }, 'options' => { 'trace' => ($VERBOSE > 3) ? 1 : 0, 'debug' => ($VERBOSE > 1) ? 1 : 0, }, ); POE::Kernel->run(); log_it("Program ended.\n"); exit(0); }
This is working as designed. In your "task_done" handler, please remove this statement: ($CLEAN_HANDLES) && $kernel->_data_handle_clear_session($session); and replace it with: delete $heap->{dss_task}{$wheel_run->ID}; delete $heap->{pid_to_wheel}{$wheel_run->PID}; delete $heap->{id_to_wheel}{$wheel_run->ID}; You should find that the session exits normally. The problem is that you are not triggering POE::Wheel::Run's destruction. The object is stored in four heap structures, and "task_done" only cleans it from three.