dchidelf has asked for the wisdom of the Perl Monks concerning the following question:

Any assistance diagnosing a crash in these Win32 Perls would be greatly appreciated!
Citrus Perl: 5.16.3
Active Perl: 5.20.2

Details:
I am trying to get a Perl script to spawn another process and have bidirectional communication with it. (like open3)

Due to IO blocking issues if I try to mix input and output (like Expect) using the standard open3 implementation I have worked out my own "ProcOpen" module that works a lot like open3, but ends up redirecting the child processes STDOUT/STDERR to file, using a reader thread for queuing that data into a Thread::Queue.

I have actually gotten it to work properly, but the Perl interpreter eventually crashes if I use my module multiple times within the run of a script.
It crashes in multiple ways under Citrus perl:
Windows popup "perl.exe has stopped working"
"Free to wrong pool {addr} not {addr} during global destruction."
NOTE: sometimes the second address is "2"
"panic: COND_DESTROY (6)."
And occasionally it just hangs when joining the second thread spawned in the script.
Under ActivePerl it has only crashed with the "perl.exe has stopped working" popup.

For testing, I am just using a simple perl script as the external process it is running.
testpipe.pl
select STDOUT; $|=1; printf "%d testpipe.pl output\n", scalar time(); while(<STDIN>) { printf "%d %s", scalar time(), $_; printf STDERR "%d YO!\n", scalar time(); } printf "%d testpipe.pl done\n", scalar time(); exit 5;
The test script looks like:
use lib '.'; use ProcOpen; my $log = IO::Handle->new_from_fd(\*STDOUT, 'w'); my $logfunc = sub { my ($lvl, $msg) = @_; chomp($msg); my $loglvl = ProcOpen::logLvlStr($lvl); print $log "ProcOpen[$loglvl] $msg\n"; }; $ProcOpen::DEBUG = 1; ProcOpen::setLogFunc($logfunc); my ($in, $out, $err); for(my $i=0;$i<20;$i++) { my $p = ProcOpen::procopen(\$in, \$out, \$err, "c:\\perl\\bin\\per +l.exe", "testpipe.pl" ); print $in "HELLO\n"; printf "FIRST: %s", scalar <$out>; print $in "HI AGAIN\n"; close($in); while(<$out>) { print "OUT $_"; } print "LOOP DONE\n"; $p->close(); } print "WORKED!!\n";
This runs the testpipe.pl process, sending "HELLO", receiving a line, sending "HI AGAIN", and then reading the rest of the output of the script. It usually crashes on one of the p->close() of the 2nd or greater iterations of the loop.

Here is my ProcOpen module
#!/usr/bin/perl package ProcOpen; use strict; use warnings; use threads; use threads::shared; use Thread::Queue; use File::Temp qw / tempfile /; use Win32API::File ':ALL'; use Win32::Process qw / STILL_ACTIVE NORMAL_PRIORITY_CLASS INFINITE / +; use IO::Select; $ProcOpen::DEBUG = 0; # set to 1 to enable debug logging $ProcOpen::LOGFUNC = sub {}; @ProcOpen::LOGLVLS = qw(STDIN STDOUT STDERR ERROR INFO DEBUG); use constant { LOGSTDIN => 0, LOGSTDOUT => 1, LOGSTDERR => 2, LOGERROR => 3, LOGINFO => 4, LOGDEBUG => 5, }; sub setLogFunc { my ($func) = @_; if (ref($func) eq "CODE") { $ProcOpen::LOGFUNC = $func; return 1; } return 0; } sub logLvlStr { my ($no) = @_; if ($no >= 0 && $no <= 5) { return $ProcOpen::LOGLVLS[$no]; } return "?"; } sub prepPrivateHandle { my ($fh) = @_; my $fd = fileno $fh; return(-1) if (! defined $fd); my $osfh = FdGetOsFHandle($fd); if ($osfh == INVALID_HANDLE_VALUE) { $ProcOpen::LOGFUNC->(ProcOpen::LOGERROR, "prepPrivateHandle: F +dGetOsFHandle failed: $^E"); return(-2); } if (! SetHandleInformation($osfh, (HANDLE_FLAG_INHERIT | HANDLE_FL +AG_PROTECT_FROM_CLOSE), 0)) { $ProcOpen::LOGFUNC->(ProcOpen::LOGERROR, "prepPrivateHandle: S +etHandleInformation failed: $^E"); return(-3); } return 0; } sub hardclose { # Previously closed underlying OS fh, but proved unnecessary my (@handles) = @_; for my $h (@handles) { $h->close(); } } sub procopen { my $self = { 'closeto' => 2000, # ms to wait for process to exit after fo +rced close 'readto' => undef, # sec to wait beforing timing out read fr +om $out / $err handles }; # If first argument is a hash it contains config options if (ref($_[0]) eq 'HASH') { my $options = shift; for (keys(%$options)) { $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBU +G, "set option: $_ = $options->{$_}"); $self->{$_} = $options->{$_}; } } my ($in, $out, $err, $cmd, @args) = @_; # created a shared variable for flagging termination of the proces +s to the threads my $running = 0; my $r_ref = share($running); $self->{'running'} = $r_ref; # create shared variables for holding the temp filenames my $poutname :shared = ""; my $perrname :shared = ""; # STDOUT and STDERR become IOQueue tied handles # Create the underlying Thread::Queues my $obuff = Thread::Queue->new(); my $ebuff = Thread::Queue->new(); # Create a thread for reading each output file (STDOUT / STDERR) $self->{'outthread'} = threads->create('fhreader', \$poutname, $ +obuff, $r_ref, ProcOpen::LOGSTDOUT); $self->{'errthread'} = threads->create('fhreader', \$perrname, $ +ebuff, $r_ref, ProcOpen::LOGSTDERR); my ($pout, $perr); # open the tempfiles for STDOUT / STDERR redirection ($pout, $poutname) = File::Temp::tempfile(); ($perr, $perrname) = File::Temp::tempfile(); if ($ProcOpen::DEBUG) { $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "STDOUT tmpfile: $pou +tname"); $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "STDERR tmpfile: $per +rname"); } # The problem with the normal open3 implementation appears to be t +hat file descriptors # are dupped into a pseudo-process when open3 calls system(1, cmd. +..). # That "process" holds onto those descriptors, so even if we close + them when open3 # returns, our Perl process still has them open, which causes bloc +king # simulate what open3 does on Windows # * swap out the STD* file descriptors # * Spawn the program (asychronously) (use Win32::Process::Create +rather than system(1, ...)) # * swap the file descriptors back # Save copies of STDIN, STDOUT, STDERR my $saveIN = IO::Handle->new_from_fd(\*STDIN, 'r'); my $saveOUT = IO::Handle->new_from_fd(\*STDOUT, 'w'); my $saveERR = IO::Handle->new_from_fd(\*STDERR, 'w'); # create a pipe for the process STDIN pipe STDIN, my $inwrite; # prevent subprocess from inheriting our write handle # Not doing so can result in a deadlock prepPrivateHandle($inwrite); $inwrite->autoflush(1); # redirect STDOUT / STDERR (dup our tmpfile handles) # Then close our do not need the IO:File handles anymore STDOUT->fdopen($pout, 'w'); STDERR->fdopen($perr, 'w'); ProcOpen::hardclose($pout, $perr); # disable output buffering STDOUT->autoflush(1); STDERR->autoflush(1); # Start the sub-process my $fullCmd = join(" ", $cmd, @args); my $subproc; Win32::Process::Create($subproc, $cmd, $fullCmd, 1, Win32::Process +::NORMAL_PRIORITY_CLASS, ".") || die ErrorReport(); # Restore the original STDIN, STDOUT, STDERR ProcOpen::hardclose(\*STDIN, \*STDOUT, \*STDERR); # we don't want +the spawned process's STDs STDIN->fdopen($saveIN, 'r'); STDOUT->fdopen($saveOUT, 'w'); STDERR->fdopen($saveERR, 'w'); ProcOpen::hardclose($saveIN, $saveOUT, $saveERR); # close the orig +inal saves because we dup'd my $pid = $subproc->GetProcessID(); $self->{'subproc'} = $subproc; $self->{'pid'} = $pid; $ProcOpen::LOGFUNC->(ProcOpen::LOGINFO, "Started '$fullCmd' PID:$p +id"); # Make sure our reader threads have started and are waiting for ou +r signal if ($running < 2) { my $absto = time() + 4; $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, " +Waiting for readers to begin execution"); lock($running); do { cond_timedwait($running, $absto) || last; } while ($running < 2); if ($running < 2) { $ProcOpen::LOGFUNC->(ProcOpen::LOGERROR, "Subprocess reade +r threads did not start!"); } elsif ($ProcOpen::DEBUG) { $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "Readers initiali +zed"); } } # notify the freader threads that they are ready to read $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "Sign +alling STDOUT reader to start"); { lock($poutname); cond_signal($poutname); } $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "Sign +alling STDERR reader to start"); { lock($perrname); cond_signal($perrname); } # create an IOQueue tied handle for each of the thread queues and +set the user's handles tie *OFH, "IOQueue", $obuff, $self; tie *EFH, "IOQueue", $ebuff, $self; $$out = \*OFH; $$err = \*EFH; # create a ProcInHandle tied handle for controlling the input stre +am and set the user handle tie *IFH, "ProcInHandle", $inwrite, $r_ref; $$in = \*IFH; $self->{'inh'} = \*IFH; bless $self; } sub close { my $self = shift; #$ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "RC +value: " . $self->{'rc'}); if (! defined $self->{'rc'}) { $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, " +Requested close on ProcOpen Object"); my $rc; my $othr = $self->{'outthread'}; my $ethr = $self->{'errthread'}; my $subproc = $self->{'subproc'}; my $inh = $self->{'inh'}; my $pid = $self->{'pid'}; print "About to close\n"; # Is our side of the subproc STDIN pipe still open? if (defined $inh && $inh->opened()) { print "It's open\n"; close($inh); # close our subprocess stdin } $subproc->GetExitCode($rc); if ($rc == Win32::Process::STILL_ACTIVE) { $subproc->Wait($self->{'closeto'}); $subproc->GetExitCode($rc); if ($rc == Win32::Process::STILL_ACTIVE) { $ProcOpen::LOGFUNC->(ProcOpen::LOGINFO, "Sending KILL +to subprocess PID:$pid"); $subproc->Kill(0); $subproc->Wait($self->{'closeto'}); $subproc->GetExitCode($rc); if ($rc == Win32::Process::STILL_ACTIVE) { $ProcOpen::LOGFUNC->(ProcOpen::LOGINFO, "Subproces +s PID:$pid still active"); $rc = -1; } } } $self->{'rc'} = $rc; print "Join Threads $othr $ethr\n"; $othr->join(); print "part done\n"; print "Join Threads $othr $ethr\n"; $ethr->join(); print "Threads joined\n"; } return $self->{'rc'}; } sub fhreader { my ($filename, $ioQueue, $running, $logtype) = @_; # purely a worker, no reason to keep these open STDOUT->close(); STDERR->close(); STDIN->close(); { # wait for the main thread to signal us to start (filename is +ready) lock($$filename); # need to make sure the main thread knows we are waiting { lock($$running); ++$$running; cond_signal($$running); } cond_wait($$filename); } my $fh = IO::File->new($$filename, 'r'); if (! $fh) { $ProcOpen::LOGFUNC->(ProcOpen::LOGERROR, "Could not open fhrea +der file $$filename"); } else { # tail the file until the spawned process has terminated # Appending each line to the ioQueue my $loopcnt = 0; while($$running) { while(<$fh>) { $ioQueue->enqueue($_); $ProcOpen::LOGFUNC->($logtype, $_); } # Log a WAITING debug message every second (every 10 times +) $ProcOpen::DEBUG && ($loopcnt++%10==0) && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "WAITING FOR +" . ProcOpen::logLvlStr($logtype)); select(undef,undef,undef,0.1); # sleep 0.1 seconds seek($fh, 0, 1); # clear EOF $fh->clearerr(); # clear EOF } # one more attempt to read anything written in the final 0.1 s +econds while(<$fh>) { $ioQueue->enqueue($_); $ProcOpen::LOGFUNC->($logtype, $_); } $fh->close(); unlink($$filename); $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, P +rocOpen::logLvlStr($logtype) . " CLOSED"); } # can not read anymore, so shutdown queue as well $ioQueue->end(); threads->exit(); } package ProcInHandle; use Win32API::File ':ALL'; sub TIEHANDLE { my $class = shift; my $fh = shift; my $running = shift; my $self = {}; $self->{'fh'} = $fh; bless $self, $class; } sub FILENO { my ($self) = @_; return $self->{'fh'}->fileno(); } sub WRITE { my ($self, @args) = @_; $ProcOpen::LOGFUNC->(ProcOpen::LOGSTDIN, join('',@args)); return $self->{'fh'}->write(@args); } sub PRINT { my ($self, @args) = @_; $ProcOpen::LOGFUNC->(ProcOpen::LOGSTDIN, join('',@args)); return $self->{'fh'}->print(@args); } sub PRINTF { my ($self, @args) = @_; $ProcOpen::LOGFUNC->(ProcOpen::LOGSTDIN, sprintf(@args)); return $self->{'fh'}->printf(@args); } sub CLOSE { my ($self) = @_; $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "STDI +N Closed"); $self->{'fh'}->flush(); $self->{'fh'}->close(); return 1; } package IOQueue; sub TIEHANDLE { my $class = shift; my $buff = shift; my $procopen = shift; my $self = {}; $self->{'buff'} = $buff; $self->{'readto'} = $procopen->{'readto'}; $self->{'subproc'} = $procopen->{'subproc'}; $self->{'running'} = $procopen->{'running'}; bless $self, $class; } sub EOF { my $self = shift; my $i = $self->{'buff'}->pending(); if (! defined $i) { return 1; } return 0; } sub READLINE { my ($self) = @_; my $to = 14400; my $subproc = $self->{'subproc'}; my $run_ref = $self->{'running'}; if ($$run_ref) { # check if the process is still running my $rc; $subproc->GetExitCode($rc); if ($rc != Win32::Process::STILL_ACTIVE) { $$run_ref = 0; $ProcOpen::DEBUG && $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "Subprocess has e +xited with rc:$rc"); } elsif ($ProcOpen::DEBUG) { $ProcOpen::LOGFUNC->(ProcOpen::LOGDEBUG, "Subprocess still + running"); } } if (defined $self->{'readto'}) { $to = $self->{'readto'}; } if (wantarray) { my @lines = (); while(my $x = $self->{'buff'}->dequeue_timed($to)) { push(@lines, $x); } return @lines; } else { my $x = $self->{'buff'}->dequeue_timed($to); return $x; } } 1;

Replies are listed 'Best First'.
Re: Having Win32 Perl crashes using ithreads
by Athanasius (Archbishop) on Feb 16, 2016 at 13:34 UTC

    Hello dchidelf, and welcome to the Monastery!

    I can’t pretend to understand what your code is doing, but I’ve done some debugging on it and I’ll share my findings in the hope that they help. My system is as follows:

    • Windows 8.1, 64-bit.
    • Strawberry Perl:
      22:56 >perl -v This is perl 5, version 22, subversion 1 (v5.22.1) built for MSWin32-x +64-multi-thread ...

    First, a couple of minor problems: (1) There’s a semicolon missing in the test script at the end of the line: print $in "HI AGAIN\n". (2) The second return statement in ProcInHandle::PRINT can never be reached.

    Now, to the main issue. When I run the test script it either crashes (“Perl interpreter has stopped working”) or reports panic: COND_DESTROY (6). But whereas for you the interpreter ”eventually crashes,” I’ve found that if I change the for loop in the test script to make just two iterations, the script reliably crashes on the second.

    The crash occurs during the call $p->close(); on the second iteration of the for loop. Further, within ProcOpen::close the crash occurs in the line $othr->join(). If I comment out that line, the crash occurs in the line $ethr->join(). And if I comment out both of those lines, the script runs successfully to completion after 25 iterations, but of course leaves active threads:

    ... LOOP DONE ProcOpen[DEBUG] Requested close on ProcOpen Object About to close Join Threads threads=SCALAR(0x28d9080) threads=SCALAR(0x28da680) part done Join Threads threads=SCALAR(0x28d9080) threads=SCALAR(0x28da680) Threads joined WORKED!! Perl exited with active threads: 0 running and unjoined 50 finished and unjoined 0 running and detached 23:27 >

    Maybe this will give you (or one of the other monks) a clue as to what is going wrong.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Having Win32 Perl crashs using ithreads
by BrowserUk (Patriarch) on Feb 16, 2016 at 14:32 UTC

    I suspect -- and it is nothing more than a suspicion, I've not run the code, just read it and Athanasius' reply above -- that by the time you are calling $[e|o]thr->close(), the threads have already committed suicide, because they've finished reading and fallen off the end of fhreader() calling threads->exit(); on their way through.

    And, if I'm correct, the later attempts to join those threads is resulting in the panic (in the version of perl you are using) or at least an attempt to use or clean up a variable that has already gone to the bit bucket. And from the wrong thread to boot.

    I can't offer more at this time as:

    1. I don't have 5.16 installed here.

      It would be a not inconsiderable amount of effort to do so; and right now I'm up to my eyes in stuff that leaves me with little mental capacity for looking at onything else. (I just happened to have a hour or so to kill whilst stuff runs :)

    2. I've never used threads->exit.

      It is one of a set of belated extensions to the api that have never seemed to be in the spirit of the original module and I've never found the need to use.

      In addition, every piece of code I seen that did make use of those belated extension to the api has exhibited weird problems.

    In your case, as the next thing you do is to fall off the end of the threadproc -- which as the description of threads->exit() states is: "The usual method for terminating a thread is to return() from the entry point function with the appropriate return value(s).", there seems no benefit to calling it.

    Having typed that lot, I further suspect that exit fails to initialise any return value; and it is the absence of that that causes the failure when join is called.

    If I'm right, the simple answer is just discard the calls to threads->exit.

    However, you are also make extensive use of condition variables and signaling. These are something else that I've studiously avoided because, back in the early days at least, they were as flaky as Adobe software. And even a lot more recently they were still apt to cause problems.

    I developed a set of methods that avoided them for the most part and have never seen the need or benefit in moving away from them.

    All of which is just to say; even if removing the threads->exit works, I have no confidence in the rest of the code working as required, because of its dependence on cond_* calls and my lack of knowledge regarding their current state of reliability.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I knew I was on the right track :)
    In the absence of evidence, opinion is indistinguishable from prejudice.
      I replaced the threads->exit() with a return(0); and the crashes are still occurring the same way. From my understanding threads->exit() functioned the same as return(undef).
      As far as the use of the cond_* functions, they are used internally in Thread::Queue, which was in use in the other thread you pointed towards.
      I'm trying to work backwards from the apparent win32 VMem corruption resulting in the "free to wrong pool", to see what might have overrun some memory.
      I'm usually a Unix programmer, so debugging under Windows is a bit out of my comfort zone.
      Let me know if there are other things to try.
Re: Having Win32 Perl crashs using ithreads
by marioroy (Prior) on Feb 16, 2016 at 19:48 UTC

    Hello dchidelf, and welcome to the monastery.

    The following resolves the issue for me. Basically, I'm able to iterate through the loop 20 times. It looks like the *tied* $out and $err handles are not being garbage collected by Perl. Thus, likely memory leaking.

    Add a CLOSE method to the IOQueue package.

    sub CLOSE { undef $_[0]; }

    Inside the test script, close $out and $err handles before calling $p->close().

    print "LOOP DONE\n"; close $out; close $err; $p->close();

    The above passes on a Windows 7 VM with Strawberry Perl 5.14.x, 5.16.x, 5.18.x, 5.20.x, and 5.22.x. You may already know this, but $^X is a special variable in Perl. It provides the path for the Perl interpreter.

    # my $p = ProcOpen::procopen(\$in, \$out, \$err, "c:\\perl\\bin\\perl. +exe", "testpipe.pl"); my $p = ProcOpen::procopen(\$in, \$out, \$err, $^X, "testpipe.pl");

    Your *cool* module is interesting.

    Regards, Mario

      Thank you *SO* much Mario!
      I have been heads down digging through perl internals trying to diagnose the crash and didn't see your response.

      Works for me as well!
      I might dig in still to see why it is leading to a crash after just 2 iterations without freeing the IOQueue objects, but it at least gets my project back on track again.

      Thanks again!
      Chad

        Hello dchidelf,

        The following is helpful if wanting the ProcOpen module to free up the handles automatically. Basically, this becomes important when omitting the closing of $out and $err handles at the application level.

        Save the $out and $err handles after construction inside the procopen method.

        tie *OFH, "IOQueue", $obuff, $self; tie *EFH, "IOQueue", $ebuff, $self; $$out = $self->{'outh'} = \*OFH; $$err = $self->{'errh'} = \*EFH;

        Inside the close method, *untie* the $out and $err handles prior to joining the threads associated with the handles.

        if (! defined $self->{'rc'}) { ... my $inh = $self->{'inh'}; my $outh = $self->{'outh'}; my $errh = $self->{'errh'}; my $pid = $self->{'pid'}; print "About to close\n"; ... $self->{'rc'} = $rc; untie *{$outh}; untie *{$errh}; print "Join Threads $othr $ethr\n"; $othr->join(); print "part done\n"; print "Join Threads $othr $ethr\n"; $ethr->join(); print "Threads joined\n"; ...

        Well, that works just as well. IMHO, leave the *CLOSE* method inside IOQueue in the event one chooses to close the handles at the application level.

        The above changes makes ProcOpen resilient to applications omitting the closing of $out and $err handles.

        Regards, Mario