2009-12-26 7 views
6

Sto scrivendo uno script Perl che scriverà alcuni input e invierà quegli input a un programma esterno. C'è un piccolo, ma non zero possibilità che questo programma si bloccherà, e voglio tempo fuori:Come posso sospendere un processo biforcato che potrebbe bloccarsi?

my $pid = fork; 
if ($pid > 0){ 
    eval{ 
     local $SIG{ALRM} = sub { die "TIMEOUT!"}; 
     alarm $num_secs_to_timeout; 
     waitpid($pid, 0); 
     alarm 0; 
    }; 
} 
elsif ($pid == 0){ 
    exec('echo blahblah | program_of_interest'); 
    exit(0); 
} 

Così com'è ora, dopo $ num_secs_to_timeout, program_of_interest persiste. Ho cercato di ucciderlo nella subroutine anonimo per $SIG{ALRM} come segue:

local $SIG{ALRM} = sub{kill 9, $pid; die "TIMEOUT!"} 

ma questo non fa nulla. program_of_interest è ancora persistente. Come faccio a uccidere questo processo?

risposta

8

Sono stato in grado di terminare correttamente il processo exec() edita da uccidendo il gruppo di processi, come mostrato nella risposta alla domanda In perl, killing child and its children when child was created using open. Ho modificato il mio codice come segue:

my $pid = fork; 
if ($pid > 0){ 
    eval{ 
     local $SIG{ALRM} = sub {kill 9, -$PID; die "TIMEOUT!"}; 
     alarm $num_secs_to_timeout; 
     waitpid($pid, 0); 
     alarm 0; 
    }; 
} 
elsif ($pid == 0){ 
    setpgrp(0,0); 
    exec('echo blahblah | program_of_interest'); 
    exit(0); 
} 

Dopo il timeout, il programma_di_interesse viene eliminato correttamente.

+0

Penso che "kill 9, - $ PID" dovrebbe essere "kill -9, $ PID". Ho usato invece il sistema ("kill -9 $ PID"). – Boolean

+0

Penso che "kill -9 $ PID" dovrebbe essere "kill -9 $ pid", ma sì, il pid negativo indica di uccidere un gruppo di processi invece di un semplice processo. Dalla documentazione dell'uccisione: "A differenza della shell, se SIGNAL è negativo, uccide i gruppi di processi anziché i processi. (Nel sistema V, un numero di processo negativo ucciderà anche i gruppi di processi, ma non è portatile.)" – simpleuser

2

Hmmm il codice funziona per me, dopo alcune modifiche secondarie, che presumo siano modifiche apportate dall'utente per rendere il codice un esempio generico.

In modo che mi lascia con due idee:

  1. è stato rimosso il problema quando è stato creato il codice di esempio - provare a creare un piccolo campione che corre in realtà (ho dovuto cambiare 'program_of_interest' e $ num_secs_to_timeout a veri valori per testarlo). Assicurati che il campione abbia lo stesso problema.
  2. È qualcosa a che fare con il programma_di_interesse che stai utilizzando - per quanto ne so, non puoi mascherare un kill 9, ma forse c'è qualcosa da fare. Hai provato a testare il tuo codice con uno script davvero semplice. Ne ho creato uno per i miei test che va mentre (1) {print "hi \ n"; dormire 1; }
  3. Qualcos'altro.

Buona fortuna ...

+0

Sembra che tu avevi ragione: Io in realtà convogliata qualcosa in program_of_interest: system ("eco blahblah | program_of_interest"). sh veniva eseguito e ucciso correttamente, ma non program_of_interest .. –

1

L'unico modo SIGKILL può essere ignorato è se il processo è bloccato in una chiamata di sistema che è continuità. Controllare lo stato del processo bloccato (con ps aux) se lo stato è D, quindi il processo non può essere eliminato.

Si potrebbe anche voler controllare che la funzione venga chiamata emettendo qualcosa da esso.

2

Il codice precedente (di strictrude27) non ha funzionato immediatamente, perché - $ PID è scritto in maiuscolo. (BTW: c'è anche: http://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html)

Ecco un esempio con il test:

#!/usr/bin/perl 
use strict; 
use warnings; 
use File::Basename; 

my $prg = basename $0; 
my $num_secs_sleep = 2; 
my $num_secs_to_timeout = 1; 
my $orig_program = "sleep $num_secs_sleep; echo \"Look ma, survived!\""; 
my $program = $orig_program; 
my $expect = ""; 

if (@ARGV){ 
    if($ARGV[0] eq "test"){ 
    test(); 
    exit 0; 
    } elsif (@ARGV == 1) { 
    $num_secs_to_timeout = $ARGV[0]; 
    } elsif (@ARGV == 2) { 
    $program = $ARGV[0]; 
    $num_secs_to_timeout = $ARGV[1]; 
    } else { 
    die "Usage: $prg [ \"test\" | [program] seconds ] " 
    } 
} 

if($orig_program eq $program) { 
    if(@ARGV < 2) { 
    $expect = $num_secs_to_timeout > $num_secs_sleep ? 
     "(we expected to survive.)" : "(we expected to TIME OUT!)"; 
    } 
    print STDERR "sleeping: $num_secs_sleep seconds$/"; 
} 

print STDERR <<END; 
    timeout after: $num_secs_to_timeout seconds, 
    running program: '$program' 
END 

if($orig_program eq $program) { 
    print STDERR "$expect$/"; 
} 

exit Timed::timed($program, $num_secs_to_timeout); 

sub test { 
    eval "use Test::More qw(no_plan);"; 
    my $stdout; 
    close STDOUT; 
    open STDOUT, '>', \$stdout or die "Can't open STDOUT: $!"; 
    Timed::timed("sleep 1", 3); 
    is($stdout, undef); 
    Timed::timed("sleep 2", 1); 
    is($stdout, "TIME OUT!$/"); 
} 

################################################################################ 
package Timed; 
use strict; 
use warnings; 

sub timed { 
    my $retval; 
    my ($program, $num_secs_to_timeout) = @_; 
    my $pid = fork; 
    if ($pid > 0){ # parent process 
    eval{ 
     local $SIG{ALRM} = 
     sub {kill 9, -$pid; print STDOUT "TIME OUT!$/"; $retval = 124;}; 
     alarm $num_secs_to_timeout; 
     waitpid($pid, 0); 
     alarm 0; 
    }; 
    return defined($retval) ? $retval : $?>>8; 
    } 
    elsif ($pid == 0){ # child process 
    setpgrp(0,0); 
    exec($program); 
    } else { # forking not successful 
    } 
}