Use alarm to set a timeout for reading stdin - perl

I have this code:
#!/usr/bin/perl
use strict;
use warnings;
my ($timeout, $size, $buffer) = (10, 10, undef);
eval {
local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
alarm $timeout;
my $nread = sysread STDIN, $buffer, $size;
# !!!! race condition !!!!!
alarm 0;
print "$nread: $buffer";
};
if ($#) {
warn $#;
}
Is it correct?
May be there is a race condition between 8 and 9 line?

Let's look, what's going on:
my ($timeout, $size, $buffer) = (10, 10, undef);
eval {
#establish ALRM signal handler
local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
#send alarm signal to program in 10 second
alarm $timeout;
#try yo read 10 bytes of data into $buffer
my $nread = sysread STDIN, $buffer, $size;
#cancel the previous timer without starting a new one
#if we returned from sysread. Yes, if 10 seconds pass
#before the next function is executed, the script will
#die even though the data was read
alarm 0;
#print number of bytes read (will be 10) and the string,
#read from input
print "$nread: $buffer";
};
$# is set if the string to be eval-ed did not compile, or if Perl code executed during evaluation die()d. In these cases the value of $# is the compile error, or the argument to die:
if ($#) {
warn $#;
}
So, this will print die message "alarm\n" if we didn't return from sysread in 10 second.
In the very unlikely case, when the input will be received just before 10 seconds elapse and we won't be able to run alarm 0;, I suggest to use the following code:
my ($timeout, $size, $buffer) = (10, 10, undef);
#I define $nread before the signal handler as undef, so if it's defined
#it means, that sysread was executed and the value was assigned
my $nread = undef;
eval {
local $SIG{ALRM} = sub {
#if it's not defined - it means, that sysread wasn't executed
unless(defined($nread))
{
die "alarm\n";
}
};
alarm $timeout;
$nread = sysread STDIN, $buffer, $size;
alarm 0;
print "$nread: $buffer";
};
Unfortunately, it doesn't save us from the case, when assignment operator wasn't executed.
Links:
http://perldoc.perl.org/functions/alarm.html
http://perldoc.perl.org/perlvar.html
http://perldoc.perl.org/functions/sysread.html

Your usage of alarm introduces a potential race condition.
The normal solution is to add alarm 0; after your eval block, so if the first alarm 0 isn't executed, you could still close the alarm.
Or you can use Time::Out package on CPAN to wrap up your code, it's better and safer.

What OS are you running this on? What version of perl?
Works fine for me on Mac OS X 10.8.3 with perl 5.12.4.
If you're using perl on Windows, you'll find that signals don't work the same as on POSIX and POSIX-like operating systems, and you might need to use the 4-argument version of select() instead.

Related

Perl TIMEOUT output message

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.

Perl subroutine timeout

I have a subroutine that normally takes 1 second to run. Sometimes, it can run infinitely. I want to move on in the code if the subroutine is taking too long (> 10 seconds) and ignore that run of that subroutine. Here is what I have so far using alarm.
use Win32::OLE;
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
alarm 10; # schedule alarm in 10 seconds
&do_the_subroutine;
alarm 0; # cancel the alarm
};
if ($#) {
$error_string .= $script;
#Do something else if the subroutine took too long.
}
do_the_subroutine{
# use existing instance if Excel is already running
eval {$ex = Win32::OLE->GetActiveObject('Excel.Application')};
die "Excel not installed" if $#;
unless (defined $ex) {
$ex = Win32::OLE->new('Excel.Application', sub {$_[0]->Quit;})
or die "Oops, cannot start Excel";
}
# get a new workbook
$book = $ex->Workbooks->Add;
# write to a particular cell
$sheet = $book->Worksheets(1);
$sheet->Cells(1,1)->{Value} = "foo";
# write a 2 rows by 3 columns range
$sheet->Range("A8:C9")->{Value} = [[ undef, 'Xyzzy', 'Plugh' ],
[ 42, 'Perl', 3.1415 ]];
# print "XyzzyPerl"
$array = $sheet->Range("A8:C9")->{Value};
for (#$array) {
for (#$_) {
print defined($_) ? "$_|" : "<undef>|";
}
print "\n";
}
# save and exit
$book->SaveAs( 'test.xls' );
undef $book;
undef $ex;
}
&do_the_subroutine never returns so I'm not able to move on. I'm also not able to put this block of code inside that subroutine. Any thoughts?
I suspect that what you want to do is simply not natively possible with alarm on Windows.
From perldoc perlport:
alarm Emulated using timers that must be explicitly polled whenever
Perl wants to dispatch "safe signals" and therefore cannot
interrupt blocking system calls. (Win32)

Alarm is not triggering at time - perl

I have used an Alarm function in my script which is not triggering at the time which it should be :
Here is my code :
$SIG{ALRM} = sub{
print"*****Test Fail*****";
};
eval{
alarm(10);
getTheBootTime();
alarm(0);
};
die $# if $#;
getTheBootTime(); is taking 5 mints to gets execute. Am i doing anything wrong here?
Assuming that getTheBootTime() is a computation and does NOT tackle with alarm and/or sleep itself, the answer is like follows.
print "something"; w/o trailing \n may not output anything, as printed string gets stuck in the buffer until a newline is printed. This is the default behavior (unless $| is set to true or specific file descriptor has autoflush turned on).
Also the specified $SIG{ALRM} does NOT interrupt execution (no die there) which is what eval/alarm combination expects.
So the following may be in $SIG{ALRM}:
$SIG{ALRM} = sub {
print "alarm, no interruption, newline\n";
};
or
$SIG{ALRM} = sub {
die "alarm, interruption";
};

Signal handler not working with double eval

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

Why doesn't die in alarm signal handler kill the process?

From How can I specify timeout limit for Perl system call?
eval {
local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
alarm $timeout;
$nread = sysread SOCKET, $buffer, $size;
alarm 0;
};
if ($#) {
die unless $# eq "alarm\n"; # propagate unexpected errors
# timed out
}
else {
# didn't
}
If a timeout happens, should sub { die "alarm\n" }; cause the end of a process. I guess I am not able to understand die. This http://www.cs.cf.ac.uk/Dave/PERL/node111.html says that "The die() function is used to quit your script and display a message for the user to read". However, in the case of the script above, the script will process the code in #timed out. Also sysread continues to work. Instead of sysread, I had a perl script that slept for 30 seconds. My timeout was set to 10 seconds. As expected, the code in #timed out is executed but the script continued to sleep.Any inputs appreciated
die doesn't cause the end of a process, it throws an exception.
Now, if nothing catches an exception, that ends a process, but you have code in place to catch this very exception.
The process doesn't end because you explicitly prevent it from ending.
Since you're not very clear on what behaviour you are getting, there could be another possibility: That you are using a Windows build of Perl.
The alarm is a Unix system call. It's very purpose (sending a signal after a certain amount of time has passed) makes no sense on Windows since Windows doesn't have signals.
Perl emulates alarm to some extent, but only in a very limited manner. sleep could very well be the only operation that's interruptable by alarm. Otherwise, the timeout is only checked between statements.
So it won't interrupt sysread, but once sysread returns, Perl notices the timeout expired and emulate a signal then.
From man alarm
alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.
Before sigalarm is delivered execution reaches else block. Insert a STDIN before sysread so that sigalarm triggers resulting expected results.
"Instead of sysread, I had a perl script that slept for 30 seconds. My
timeout was set to 10 seconds. As expected, the code in #timed out is
executed but the script continued to sleep."
Really?
#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);
eval {
open my $fh, '<', $0 || die;
local $SIG{ALRM} = sub {
print STDERR "hello!\n";
die "bye!";
};
alarm 3;
while (<$fh>) {
print $_;
sleep 1;
}
close $fh;
};
if ($#) {
print "HERE: $#\n";
}
The output:
#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);
hello!
HERE: bye! at ./test.pl line 9, <$fh> line 3.
Over in the expected 3 seconds; this is still the case if I just use "sleep 100" instead of the file read. Note that if you spawn a subprocess, alarm will not kill that and the parent process must wait. In that case, the "hello!" in the signal handler will appear when alarm fires, but the eval which catches the die will not complete until the subprocess does.
I had the same issue when porting a Linux Perl script to Windows.
I solved it by ...
Creating a non-blocking socket
$recsock = IO::Socket::INET->new(
LocalPort => 68,
Proto => "udp",
Broadcast => 1,
Blocking => 0,
) or die "socket: $#";
Adding $continue variable to the timeout handle
# Timeout handle
$SIG{ALRM} = sub {
print "timeout\n";
$continue = 1;
};
and checking for the $continue to become true when the timeout occurs:
alarm($timeout);
while(1){
$recsock->recv($newmsg, 1024);
eval {
$packet = Net::Package->new($newmsg);
...
};
sleep 0.1;
last if ($continue);
}
alarm(0);