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

Hello i Use following Code, but it don't worke for me it generates Zombies and i can't kill the program. I need the return value from the shell programm, when a timeout occurs it should return Timeout

my $command='whatever.....'; my $status; eval { local $SIG{ALRM} = sub { die("dead\n"); }; alarm 2; my $status=`$command`; alarm(0); # turn off the alarm clock }; if($@){ if($@ eq "dead\n"){ $status='Timeout'; }else{ chomp($status); } } return $status;

I read many thinks, but found no soloution. Problem is i need the exit code from the shell command, so i can't change the code to fork/exec. Any hints or other solutions? The Main Program runs as an SNMP Agent, and i call external Programs that checks the System. I need the Timeout.....

Replies are listed 'Best First'.
Re: Need Function for Timing out shell commands
by ikegami (Patriarch) on Sep 14, 2011 at 22:15 UTC
Re: Need Function for Timing out shell commands
by eyepopslikeamosquito (Archbishop) on Sep 14, 2011 at 21:49 UTC
Re: Need Function for Timing out shell commands
by thewebsi (Scribe) on Sep 14, 2011 at 22:13 UTC

    I gather from eyepopslikeamosquito's post that a pure-Perl solution that both kills the process on timeout and captures its exit status otherwise is non-trivial.

    Not sure what O/S you are using. In Unix/Linux, I would be surprised to find any networking command that doesn't have a timeout option, so I would check that first. Alternatively, there is the timeout command, which would solve your problem entirely.

      I gather from eyepopslikeamosquito's post that a pure-Perl solution that both kills the process on timeout and captures its exit status otherwise is non-trivial.

      It is not trivial, but is is not so complex either:

      # untested! use POSIX qw(WNOHANG _exit); use Errno qw(ECHILD); local $SIG{CHLD} = sub {}; # this do-nothing sub ends the # select below on SIGCHLD my $pid = fork; if (!$pid) { defined $pid or die "fork failed: $!"; do { exec @cmd }; # warnings friendly! _exit(1); } my $start = time; while (1) { my $rc = waitpid ($pid, WNOHANG); last if $rc == $pid; last if $rc < 0 and $! == ECHILD; select (undef, undef, undef, 1); kill INT => $pid if time > $start + $timeout; }

      Using AnyEvent it would be even easier.

Re: Need Function for Timing out shell commands
by cdarke (Prior) on Sep 15, 2011 at 07:40 UTC
    I need the return value from the shell programm

    You cannot use return from a main program, exit should be used instead (did you use warnings and strict?). Problem with exit though is that you can only return a number (between 0..255), not text. If you must return text:

    Replace return $status with print $status
    In the shell (Bash or Korn):
    /home/fred$ result=$(perl gash.pl) /home/fred$ echo $result Timeout
Re: Need Function for Timing out shell commands
by salva (Canon) on Sep 15, 2011 at 07:54 UTC
    Problem is i need the exit code from the shell command, so i can't change the code to fork/exec

    There is no such limitation when using fork/exec. The waitpid built-in lets you retrieve the exit status of the child.

    update: if you want to get the output from the program, you would like to launch the process using open:

    my $pid = open my $fh, '-|', @cmd or die "open failed $!";
    And then use a select loop to read from $fh while checking that it does not time out or otherwise send a signal to the process.

      Ok let me explain it clearer. i've a main perl program which loops and execute commands (run's as a daemon), so when i execute with `cmd....` system and so so on i become zombies...... excample prog what i call

      test.pl #!/usr/bin/perl $status='OK'; print $status; 1; test2.pl #!/usr/bin/perl $status='124455566345'; print $status; 1; so here i get zombies, problem is i need the print $status; in daemon.pl, that can be ascii strings or numerics.... daemon.pl #!/usr/bin/perl while(1){ my $command='test1.pl or test2.pl'; my $status; eval { local $SIG{ALRM} = sub { die("dead\n"); }; alarm 2; my $status=`$command`; alarm(0); # turn off the alarm clock }; if($@){ if($@ eq "dead\n"){ $status='Timeout'; }else{ chomp($status); } } #do something with my $status..... sleep(5); }
        Using alarm to leave from `$cmd` prematurely leaves zombies as it jumps over the waitpid call inside that reaps the child process.
        # untested! my $pid = open my $out, '-|', $cmd or die "Open failed: $!"; local $INT{CHLD} = sub {}; my $fileno = fileno $out; my $start = time; my $str = ''; my $timedout = 0; while (1) { my $v = ''; vec($v, $fileno, 1) = 1; if (select($v, undef, undef, 1) > 0) { if (vec($v, $fileno, 1)) { sysread $v, $str, 1024, length $v or last; } } if (time - $start > $timeout) { $timedout = 1; kill INT => $pid } close ($out); print "timed out: $timedout\noutput: $str\n";