I trying to make replacement for timeout using perl (need in centos5)
here the script:
#!/usr/bin/perl
use strict;
my $pid=$$;
my $timeout=shift;
my #args=#ARGV;
eval {
local $SIG{ALRM} = sub {
print "Timed OUT!\n";
exit 142;
kill 9,$pid;
};
alarm($timeout);
system(#args);
};
exit $?;
while testing it I found:
Here all fine
time /tmp/timeout 3 sleep 6
Timed OUT!
real 0m3.007s
user 0m0.000s
sys 0m0.004s
but here all bad
time echo `/tmp/timeout 3 sleep 6`
Timed OUT!
real 0m6.009s
user 0m0.000s
sys 0m0.004s
on my debian system I tested with /usr/bin/timeout:
time echo `/usr/bin/timeout 3 sleep 6`
real 0m3.004s
user 0m0.000s
sys 0m0.000s
So the questions
why the perl script work so strange ?
is there any real working way to have timeout writen on perl which will work the same as binary timeout ?
please note, that I know about /usr/share/doc/bash-3.2/scripts/timeout and I also found that it acts the same as my perl approach
also please note that I can't install modules from CPAN on the server targeted for this script
i tried with exec() but in that case it does not handle signal in sub.
UPD
with the script from #rhj (had to fix a little)
#!/usr/bin/perl
use strict;
use warnings;
my $PID=$$;
my $timeout=shift;
my #args=#ARGV;
my $pid = fork();
defined $pid or die "fork: $!";
$pid == 0 && exec(#args);
my $timed_out = 0;
$SIG{ALRM} = sub { $timed_out = 1; die; };
alarm $timeout;
eval { waitpid $pid, 0 };
alarm 0;
if ($timed_out) {
print "Timed out!\n";
kill 9, $pid;
kill 9, $PID;
}
elsif ($#) {
warn "error: $#\n";
}
it pass above test but fail in the case of calling external script:
run_script
#!/bin/sh
sleep 6
test.sh
#!/bin/sh
a=`./timeout.pl 2 ./run_script.sh`
output
$ time ./test.sh
real 0m6.020s
user 0m0.004s
sys 0m0.008s
This version should always work:
#!/usr/bin/perl
use strict;
use warnings;
my $pid=$$;
my $timeout=shift;
my #args=#ARGV;
my $pid = fork();
defined $pid or die "fork: $!";
$pid == 0 && exec(#args);
my $timed_out = 0;
$SIG{ALRM} = sub { $timed_out = 1; die; };
alarm $timeout;
eval { waitpid $pid, 0 };
alarm 0;
if ($timed_out) {
print "Timed out!\n";
kill 9, $pid;
}
elsif ($#) {
warn "error: $#\n";
}
It does not handle an error in the exec() call, though.
Had to make it with IPC::Cmd;
#!/usr/bin/perl -w
use strict;
use IPC::Cmd qw(run_forked);
my $timeout=shift;
my $stdout;
my $hashref = run_forked(#ARGV, { timeout => $timeout});
print $hashref->{'stdout'};
print STDERR $hashref->{'stderr'};
if ($hashref->{'timeout'}) {
print STDERR "Timed out!\n";
exit 142;
}
exit $hashref->{'exit_code'};
the bad thing that I had to install IPC::Cmd using rpmforge.
Related
I'm programming a perl script to monitorize a DB with Nagios.
I'm using alarm function from Time::HiRes library for the timeout.
use Time::HiRes qw[ time alarm ];
alarm $timeout;
Everything works fine. The thing is I want to change the output message cause it returns "Temporizador" and if I do an
echo $?
Returns 142. I want to change the message in order to make an "exit 3" so it can be recognized by Nagios.
Already tried 'eval' but doesn't work.
The function that's taking the time is written in C, which precludes you from using a custom signal handler safely.
You don't appear worried about terminating your program forcefully, so I suggest you use alarm without a signal handler to terminate your program forcefully if it takes too long to run, and using a wrapper to provide the correct response to Nagios.
Change
/path/to/program some args
to
/path/to/timeout_wrapper 30 /path/to/program some args
The following is timeout_wrapper:
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw( WNOHANG );
use Time::HiRes qw( sleep time );
sub wait_for_child_to_complete {
my ($pid, $timeout) = #_;
my $wait_until = time + $timeout;
while (time < $wait_until) {
waitpid($pid, WNOHANG)
and return $?;
sleep(0.5);
}
return undef;
}
{
my $timeout = shift(#ARGV);
defined( my $pid = fork() )
or exit(3);
if (!$pid) {
alarm($timeout); # Optional. The parent will handle this anyway.
exec(#ARGV)
or exit(3);
}
my $timed_out = 0;
my $rv = wait_for_child_to_complete($pid, $timeout);
if (!defined($rv)) {
$timed_out = 1;
if (kill(ALRM => $pid)) {
$rv = wait_for_child_to_complete($pid, 5);
if (!defined($rv)) {
kill(KILL => $pid)
}
}
}
exit(2) if $timed_out;
exit(3) if $rv & 0x7F; # Killed by some signal.
exit($rv >> 8); # Expect the exit code to comply with the spec.
}
Uses the Nagios Plugin Return Codes. Timeouts should actually return 2.
You should handle the ALRM signal. For example:
#!/usr/bin/env perl
use strict;
use warnings;
use Time::HiRes qw[ time alarm ];
$SIG{ALRM} = sub {print "Custom message\n"; exit 3};
alarm 2;
sleep 10; # this line represents the rest of your program, don't include it
This will output:
18:08:20-eballes#urth:~/$ ./test.pl
Custom message
18:08:23-eballes#urth:~/$ echo $?
3
For an extended explanation about handling signals check this nice tutorial on perltricks.
My intention is to execute long.pl perl script with different path as an argument and since long.pl has indefinite loop such that in the main script it does not come to second path. I thought to use fork for doing it, but I'm not sure whether it will solve my problem or not!
Some information on the method of achieving the task would be helpful, and please let me know if you need any clarification on the problem statement.
#!/usr/bin/perl
use strict;
use warnings;
print localtime () . ": Hello from the parent ($$)!\n";
my #paths = ('C:\Users\goudarsh\Desktop\Perl_test_scripts','C:\Users\goudarsh\Desktop\Perl_test_scripts/rtl2gds');
foreach my $path(#paths){
my $pid = fork;
die "Fork failed: $!" unless defined $pid;
unless ($pid) {
print localtime () . ": Hello from the child ($$)!\n";
exec "long.pl $path"; # Some long running process.
die "Exec failed: $!\n";
}
}
long.pl
#!/usr/bin/perl
use strict;
use warnings;
while(1){
sleep 3;
#do some stuff here
}
Example run:
$ perl my_forker.pl
Done with other process.
Done with long running process.
Done with main process.
The following files must have executable permissions set:
long_running.pl:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.020;
sleep 5;
say 'Done with long running process.';
other_process.pl:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.020;
sleep 3;
say "Done with other process."
my_forker.pl:
use strict;
use warnings;
use 5.020;
my #paths = (
'./long_running.pl',
'./other_process.pl',
);
my #pids;
for my $cmd (#paths) {
defined (my $pid = fork()) or die "Couldn't fork: $!";
if ($pid == 0) { #then in child process
exec $cmd;
die "Couldn't exec: $!"; #this line will cease to exist if exec() succeeds
}
else { #then in parent process, where $pid is the pid of the child
push #pids, $pid;
}
}
for my $pid (#pids) {
waitpid($pid, 0) #0 => block
}
say "Done with main process.";
I have this code to timeout a long-running process (sleep in this case):
#!/usr/bin/env perl
use strict;
use warnings;
die "Usage: $0 SLEEP TIMEOUT\n" unless #ARGV == 2;
my ( $sleep, $timeout ) = #ARGV;
$|++;
eval {
local $SIG{ALRM} = sub { die "TIMEOUT\n" };
alarm $timeout;
eval {
# long-running process
print "Going to sleep ... ";
sleep $sleep;
print "DONE\n";
};
alarm 0; # cancel timeout
};
die $# if $#;
When I run it as ./alarm 5 2, I expect it to die saying "TIMEOUT". However it exits with 0 and says nothing. It works as expected when I remove the inner eval block (not the block's content, just the eval) though. Can someone explain why is that? Thanks.
Because you trap the error in the first eval block and the second eval block does not have an exception and clears $#.
eval {
local $SIG{ALRM} = sub { die "TIMEOUT\n" };
alarm $timeout;
eval {
# long-running process
print "Going to sleep ... ";
A: sleep $sleep;
print "DONE\n";
};
B:
alarm 0; # cancel timeout
C:};
die $# if $#;
$sleep > $timeout, so at A: your program throws a SIGALRM. The signal is caught by your local signal handler and calls die "TIMEOUT\n". So Perl sets $# to "TIMEOUT\n" and resumes execution at B:. Your program then makes it to C: without any additional errors. Since your outer eval block completed normally, Perl clears $#, and your final die statement does not execute.
To do what it seems like you want to do, you could either
don't use eval on the outer block
put another die $# if $# call at the end of the outer block
I'm trying to implement custom handlers for given keystrokes so that I can change mode when my script is fetching data from file. How is that possible without any WHILE loop?
I was looking into Term::ReadKey but I dont think it does what I need. Maybe I should connect it with something though I can't find any solution on google.
I've just started with perl scripting :)
Here is an example of how to avoid busy waiting when waiting for a keyboard input:
use strict;
use warnings;
use IPC::Open2;
my $pid1 = run_cmd('read_key');
my $pid2 = run_cmd('counter');
print "Master: waiting for keyboard event..\n";
waitpid $pid1, 0;
print "Master: Done.\n";
kill 'TERM', $pid2;
sub run_cmd {
my ($cmd) = #_;
open(OUT, ">&STDOUT") or die "Could not duplicate STDOUT: $!\n";
open(IN, ">&STDIN") or die "Could not duplicate STDIN: $!\n";
my $pid = open2('>&OUT', '<&IN', $cmd);
return $pid;
}
where read_key is:
use strict;
use warnings;
use Term::ReadKey;
ReadMode 4;
END { ReadMode 0 }
my $key = ReadKey(0);
print "$key\n";
and counter is:
use strict;
use warnings;
$SIG{TERM} = sub { die "Child (counter): Caught a sigterm. Abort.\n" };
my $i = 0;
while (++$i) {
sleep 1;
print "$i\n";
}
Example output:
Name "main::IN" used only once: possible typo at ./p.pl line 19.
Name "main::OUT" used only once: possible typo at ./p.pl line 18.
Master: waiting for keyboard event..
1
2
3
q
Master: Done.
Child (counter): Caught a sigterm. Abort.
I have a command that I'm executing using OPEN with pipe, and I want to set a timeout of 10 seconds and have the sub process aborted if the execution time exceeds this. However, my code just causes the program to hang- Why is my ALARM not getting delivered properly?
my $pid = 0;
my $cmd = "someCommand";
print "Running Command # $num";
eval {
local $SIG{ALRM} = sub {
print "alarm \n";
kill 9, $pid;
};
alarm 10;
pid = open(my $fh, "$cmd|");
alarm 0;
};
if($#) {
die unless $# eq "alarm \n";
} else {
print $_ while(<$fh>);
}
EDIT:
So From the answers below, This is what I have:
my $pid = open(my $fh, qq(perl -e 'alarm 10; exec \#ARGV; die "exec: $!\n" ' $cmd |));
print $_ while(<$fh>);
But this print ALARM CLOCK to the console when the alarm times out...whereas I dont specify this anywhere in the code...how can I get rid of this, and where would I put the custom alarm event handler?
Thanks!
I want to set a timeout of 10seconds and have the sub process aborted if the execution time exceeds this
A different approach is to set the alarm on the subprocess itself, with a handy scripting language you already have:
my $cmd = "someCommand";
my $pid = open(my $child_stdout, '-|',
'perl', '-e', 'alarm 10; exec #ARGV; die "exec: $!"', $cmd);
...
Your child process will initially be perl (well, the shell and then perl), which will set an alarm on itself and then exec (replace itself with) $someCommand. Pending alarms, however, are inherited across exec()s.
All your code is doing is setting a 10 second timeout on the open call, not on the whole external program. You want to bring the rest of your interaction with the external command into the eval block:
eval {
local $SIG{ALRM} = sub {
print "alarm \n";
kill 9, $pid;
};
alarm 10;
$pid = open(my $fh, "$cmd|");
print while <$fh>;
close $fh;
alarm 0;
};
if($#) {
die unless $# eq "alarm \n";
}