use strict;
use warnings;
print "hello\n";
sleep(10);
print "print before alarm\n";
alarm 5;
$SIG{'ALRM'} = \&alarm;
$SIG{'INT'} = \&alarm;
$SIG{'TERM'} = \&alarm;
sub alarm {
print "alarm signal hanndled\n";
}
I am not able to handle signals either for alarm or for pressing ctrl+c. I searched and find this as a way to do signal handling. What am i doing wrong?
First, set the handlers. Then, set the alarm. Last, do the time-consuming operation (sleep). You have it upside down:
#! /usr/bin/perl
use strict;
use warnings;
$SIG{'ALRM'} = \&alarm;
$SIG{'INT'} = \&alarm;
$SIG{'TERM'} = \&alarm;
print "hello\n";
alarm 5;
sleep(10);
print "after sleep\n";
sub alarm {
print "alarm signal handled\n";
}
Rather than duplicating the handler for each SIG, it's neater to use sigtrap.
This example prevents the user from killing, but you can do whatever you want in the handler. Of course, if your handler does an 'exit' you also have the END sub to do additional stuff as well.
use strict;
use warnings;
use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT);
sub sig_handler {
my $sig_name = shift;
print "You can't kill me! $sig_name caught";
}
for (1 .. 10) {
print $_ . "\n";
sleep 1;
}
Alternatively you can use the existing signal lists...
use sigtrap 'handler' => \&sig_handler, qw(any normal-signals error-signals stack-trace);
see perldoc sigtrap for more info
Related
I am trying to monitor the output of an external command with AnyEvent::Subprocess:
use feature qw(say);
use strict;
use warnings;
use AnyEvent::Subprocess;
my $job = AnyEvent::Subprocess->new(
delegates => [ 'StandardHandles', 'CompletionCondvar' ],
code => 'myscript.pl',
);
my $run = $job->run;
my $condvar = $run->delegate('completion_condvar');
$run->delegate('stdout')->handle->on_read(
sub {
my ( $handle ) = #_;
my $line = $handle->rbuf;
chomp $line;
say "Got output: '$line'";
$handle->rbuf = ""; # clear buffer
}
);
my $done = $condvar->recv;
In general, I do not have access to the source code of the external script, so I cannot insert commands like STDOUT->autoflush(1) into the script (if the script happens to be a Perl script).
Here is the test script I used for testing:
myscript.pl:
use feature qw(say);
use strict;
use warnings;
#STDOUT->autoflush(1);
sleep 1;
say "data 1";
sleep 1;
say "data 2";
sleep 1;
say "data 3";
The output is coming all at once after myscript.pl finishes. I want to print each line from myscript.pl as it becomes available. How can this be done without modifying myscript.pl ?
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.
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 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.