K
Koszalek Opalek
I know that one has to be cautious when using threads and forks in one
script/program (and that applies not only to perl). On the other hand,
a fork() followed immediately be exec() looks like a sensible thing to
do. Is there a way to do it safely in perl?
The script below uses two threads -- one spawns new processes and the
other watches the processes in the %PIDS hash. (It could be easily
rewritten without threads but that is beside the point). The script
usually crashes after just a few seconds under perl 5.8.9. (verified
on Linux/Fedora and FreeBSD). The crash happens also after commenting
out the warn statements.
Is it possible to fix this?
#!/usr/bin/perl
use strict;
use warnings;
use threads;
use threads::shared;
use POSIX ":sys_wait_h";
use constant {
RUNNING => 1,
ERROR => 2
};
my %PIDS : shared = ();
my $STATE : shared = RUNNING;
$| = 1;
$SIG{'CHLD'} = \&reaper;
sub reaper {
lock( %PIDS );
while( my $pid = waitpid( -1, WNOHANG ) ) {
if( WIFEXITED( $? )) {
delete $PIDS{$pid};
}
}
$SIG{'CHLD'} = \&reaper;
}
sub watcher_loop {
warn sprintf( "Thread created: %s", (caller 0)[3]);
my $state = RUNNING;
while( $state == RUNNING ) {
{
lock( %PIDS );
my( $pid, $time );
while (my( $pid, $time ) = ( each %PIDS )) {
if( ! kill 0, $pid ) {
warn sprintf( "Process %d disappeared from the
process list", $pid );
lock( $STATE );
$STATE = ERROR;
}
}
}
{
lock( $STATE );
$state = $STATE;
cond_timedwait( $STATE, 3 );
}
}
}
sub spawn_loop {
warn sprintf( "Thread created: %s", (caller 0)[3]);
my $state = RUNNING;
while( $state == RUNNING ) {
my $cnt;
{
lock( %PIDS );
$cnt = scalar( keys %PIDS );
}
if( $cnt < 3 ) {
warn "$cnt processes running. Will spawn a new process
now.";
my $pid;
if( $pid = fork ) {
lock( %PIDS );
$PIDS{$pid} = time;
}
else {
exec( '( ls -l; sleep 1 ) > /dev/null' );
}
}
{
lock( $STATE );
$state = $STATE;
cond_timedwait( $STATE, 3 );
}
}
}
my @threads = (
threads->new( \&watcher_loop ),
threads->new( \&spawn_loop ),
);
warn sprintf( "Joining %d threads.", scalar @threads );
for( @threads ) {
$_->join;
}
script/program (and that applies not only to perl). On the other hand,
a fork() followed immediately be exec() looks like a sensible thing to
do. Is there a way to do it safely in perl?
The script below uses two threads -- one spawns new processes and the
other watches the processes in the %PIDS hash. (It could be easily
rewritten without threads but that is beside the point). The script
usually crashes after just a few seconds under perl 5.8.9. (verified
on Linux/Fedora and FreeBSD). The crash happens also after commenting
out the warn statements.
Is it possible to fix this?
#!/usr/bin/perl
use strict;
use warnings;
use threads;
use threads::shared;
use POSIX ":sys_wait_h";
use constant {
RUNNING => 1,
ERROR => 2
};
my %PIDS : shared = ();
my $STATE : shared = RUNNING;
$| = 1;
$SIG{'CHLD'} = \&reaper;
sub reaper {
lock( %PIDS );
while( my $pid = waitpid( -1, WNOHANG ) ) {
if( WIFEXITED( $? )) {
delete $PIDS{$pid};
}
}
$SIG{'CHLD'} = \&reaper;
}
sub watcher_loop {
warn sprintf( "Thread created: %s", (caller 0)[3]);
my $state = RUNNING;
while( $state == RUNNING ) {
{
lock( %PIDS );
my( $pid, $time );
while (my( $pid, $time ) = ( each %PIDS )) {
if( ! kill 0, $pid ) {
warn sprintf( "Process %d disappeared from the
process list", $pid );
lock( $STATE );
$STATE = ERROR;
}
}
}
{
lock( $STATE );
$state = $STATE;
cond_timedwait( $STATE, 3 );
}
}
}
sub spawn_loop {
warn sprintf( "Thread created: %s", (caller 0)[3]);
my $state = RUNNING;
while( $state == RUNNING ) {
my $cnt;
{
lock( %PIDS );
$cnt = scalar( keys %PIDS );
}
if( $cnt < 3 ) {
warn "$cnt processes running. Will spawn a new process
now.";
my $pid;
if( $pid = fork ) {
lock( %PIDS );
$PIDS{$pid} = time;
}
else {
exec( '( ls -l; sleep 1 ) > /dev/null' );
}
}
{
lock( $STATE );
$state = $STATE;
cond_timedwait( $STATE, 3 );
}
}
}
my @threads = (
threads->new( \&watcher_loop ),
threads->new( \&spawn_loop ),
);
warn sprintf( "Joining %d threads.", scalar @threads );
for( @threads ) {
$_->join;
}