Subject: | exit_status() can fail to return valid exit state after sufficently long runtime. |
Problem:
A main process that runs sufficiently long might get an 'undef' or -1
form exit_status() instead of the real execution status.
Problem reproduced using the attached program under:
Solaris 10 / Proc::Simple 1.26 / Perl 5.12.2 / x86-arch
SuSE 11.2 / Proc::Simple 1.26 / Perl 5.10.0
AFAIK, all versions of Proc::Simple are affected.
Analysis:
If a Proc::Simple variable/object is DESTROYed while the associated
Proc::Simple-process has already terminated, the PID is unnecessarily
remembered in %DESTROYED for later reaping.
If another process re-uses the PID (e.g. after the OS' PID wrap-around)
the process is reaped by sub THE_REAPER and the execution state is lost
since the %DESTROYED hash is checked before the %EXIT_STATUS hash there.
Suggested fix:
Do not remember the PID of an already terminated process for later
reaping - that will try to reap another process that re-used the
same PID. It might not even be the PID of a process under control of
Proc::Simple. Additionally, it slows down THE_REAPER since it finally
has to check 32k PIDs.
Suggested patch:
In sub DESTROY, change:
$DESTROYED{ $self->pid } = 1;
to:
$DESTROYED{ $self->pid } = 1 if $self->poll();
Applying the patch above fixed the problem (at least for me).
Another observation:
The associated $INTERVAL{...} could be deleted when process was reaped
by checking the PIDs from %DESTROYED or when DESTROY was called for an
already terminated process.
Thanks for this otherwise very useful module.
Subject: | proc-simple-bug.pl |
use strict;
use warnings;
use Proc::Simple; # checked with V1.26
my ($count, $problems) = (0, 0);
## uncomment to see effect immediately
# $Proc::Simple::DESTROYED{$_} = 1 for (1..33000);
for (1..33) { # assumes 32k PID-range
warn "Run $_\n";
run_and_poll() for (1..1000);
}
warn "Finis: ITERATIONS=$count PROBLEMS=$problems RUNTIME=", time() - $^T, "\n";
# Linux:
# Finis: ITERATIONS=33000 PROBLEMS=42 RUNTIME=338 (using: wait() --> -1)
# Finis: ITERATIONS=33000 PROBLEMS=1549 RUNTIME=1803 (using: poll() --> undef)
# Finis: ITERATIONS=33000 PROBLEMS=0 RUNTIME=222 (using: wait() and patch applied)
exit;
sub run_and_poll {
$count++;
my $ex = int rand 50;
my $ps = Proc::Simple->new();
$ps->start( qq{/bin/sh -c "exit $ex"} );
my $pid = $ps->pid();
# select(undef, undef, undef, 0.1) while $ps->poll(); # poll()ing --> undef
$ps->wait(); # wait()ing --> -1 (Linux)
my $ex_state = $ps->exit_status();
# DESTROY now remembers the PID of an already terminated process
return $pid if defined $ex_state and $ex_state == $ex * 256;
warn "Problem: PID=$pid EXIT-STATUS=(",
(defined $ex_state ? "$ex_state but $ex expected" : "undefined"),
") ITERATION=$count Err=$!\n",
" KEYS in \%DESTROYED: ",
scalar keys %Proc::Simple::DESTROYED,
"\n";
$problems++;
# DESTROY now remembers the PID of an already terminated process
return $pid;
}