Let's say I have an opened filehandle, or anything I have to fix before to exit the script.
I also have a long loop and I want to break the loop if the processus receives a sigint. What is the cleanest way to do this?
Here is an illustration of the problem ;
use sigtrap 'handler' => \&sigint_handler, 'INT';
sub sigint_handler {
print("\nI see you are trying to escape this evil infinite loop,
but there is nothing that I can do to help you :(\n");
}
my $the_developper_is_unable_to_end_this_mess = 1;
open(my $handle, "< some.important.file");
while($the_developper_is_unable_to_end_this_mess) {
print("And the evil loop rolls again and again...\n");
sleep(3);
}
close($handle);
print("everything went better than expected\n")
Cleanup code that should absolutely be run can be put into an END block:
END {
print "This is run before the program exits\n";
}
print "This is some normal code\n";
Output:
This is some normal code
This is run before the program exits
However, END blocks are not run when the process terminates from a signal, unless when you implement your own signal handler – and if all that it does is calling exit.
So this code won't print END when you terminate it with a SIGINT:
END { print "END\n" }
sleep 4;
But this one will:
END { print "END\n" }
local $SIG{INT} = sub { exit 1 };
sleep 4;
These handlers are dynamically scoped, so you can put one into the loop that isn't in effect on the outside:
my $run = 1;
while ($run) {
local $SIG{INT} = sub { $run = 0 };
print "zzz\n";
sleep 3;
}
print "done!\n";
Of course you can also use sigtrap:
my $run = 1;
while ($run) {
use sigtrap handler => sub { $run = 0 }, 'INT';
print "zzz\n";
sleep 3;
}
print "done!\n";
PS: Filehandles are automatically closed when they fall out of scope / on process exit. If the handle is just reading from a file, there can't be any buffering issues or other processes depending on the handle, so that you can safely forget about close $fh in this case.
Related
How to end script without using using exit if using Perl FCGI. After searching for days the only solution I found is to jump at label in the main script. below is the code of the main index.fcgi.
$fcgi_requests = 0; # the number of requests this fcgi process handled.
$handling_request = 0;
$exit_requested = 0;
$app_quit_request = 0; # End the application but not the FCGI process
# workaround for known bug in libfcgi
while (($ignore) = each %ENV) { }
$fcgi_request = FCGI::Request();
#$fcgi_request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);
sub sig_handler {
my ($callpackage, $callfile, $callline) = caller;
if ($app_quit_request) {
$app_quit_request = 0;
goto ABORTLABEL;
}
$exit_requested = 1;
exit(0) if !$handling_request;
}
$SIG{USR1} = \&sig_handler;
$SIG{TERM} = \&sig_handler;
$SIG{PIPE} = 'IGNORE';
#The goal of fast cgi is to load the program once, and iterate in a loop for every request.
while ($handling_request = ($fcgi_request->Accept() >= 0)) {
process_fcgi_request();
$handling_request = 0;
last if $exit_requested;
#exit if -M $ENV{SCRIPT_FILENAME} < 0; # Autorestart
}
$fcgi_request->Finish();
exit(0);
#=========================================================#
sub process_fcgi_request() {
$fcgi_requests++;
# dispatch current request
my_app();
$fcgi_request->Finish();
}
#=========================================================#
# let it think we are done, used by abort
ABORTLABEL:
$fcgi_request->Finish();
#=========================================================#
The main request is I want to stop the program execution from inside sub insidi modules that may be called by long depth for example inside a login function in a accounts module.
Of course I can not use exit because it will terminate the fcgi process, I tried all error and throw and try modules all use die which also ends the process. Of course I can use the return from each sub but this will require to rewrite the whole program for fcgi.
The normal way to model exceptions in Perl is to call die inside eval BLOCK, which catches the die and so doesn't terminate the process. It'll just terminate the eval and the program continues to run from immediately afterwards. As far as I've seen, the exception-handling modules on CPAN are mostly wrappers around this basic functionality to give it different syntax or make it easier to write catch blocks. Therefore I'm surprised these don't work for you. Did you actually try them or did you just assume die always kills the process? The name is slightly misleading, because it really means 'throw an exception'. Just if you do that outside an eval the interpreter catches it, and its only response is to terminate the process.
eval {
say "Hello world";
die;
say "Not printed";
};
say "Is printed";
You don't want to call exit inside an eval though. Nothing catches that.
I would recommend though rewriting the entire control flow for FCGI. The lifecycle of your code changes significantly, so you have to make a certain amount of modifications to make sure that variable re-use is working properly and you're not leaking memory. Often it's better to do that up front rather than spend days tracking down odd bugs later.
After several questions and deep research, I got this solution. This coding example allows you to return from any nested levels of calls. The module Scope::Upper is XS so it should be fast.
use Scope::Upper qw/unwind CALLER/;
sub level1 {
print "before level 1 \n";
level2();
print "after level 1 \n";
}
sub level2 {
print "before level 2 \n";
level3();
print "after level 2 \n";
}
sub level3 {
print "before level 3 \n";
level4();
print "after level 3 \n";
}
sub level4 {
print "before level 4 \n";
#unwind CALLER 2;
my #frame;
my $i;
#$i++ while #frame = caller($i);# and $frame[0] ne "main";
$i++ while #frame = caller($i);
#print "i=: $i \n";
#unwind CALLER (#frame ? $i : $i - 1);
unwind CALLER $i-1;
print "after level 4 \n";
}
print level1();
If you run this code the output will be:
before level 1
before level 2
before level 3
before level 4
You can return to any up level using:
my intLevel = 2;
unwind CALLER intLevel;
I'm writing a wrapper to an external command ("sox", if this can help) with Perl "Tk".
I need to run it asynchronously, of course, to avoid blocking tk's MainLoop().
But, I need to read it's output to notify user about command's progress.
I am testing a solution like this one, using IPC::Open3:
{
$| = 1;
$pid = open3(gensym, ">&STDERR", \*FH, $cmd) or error("Errore running command \"$cmd\"");
}
while (defined($ch = FH->getc)) {
notifyUser($ch) if ($ch =~ /$re/);
}
waitpid $pid, 0;
$retval = $? >> 8;
POSIX::close($_) for 3 .. 1024; # close all open handles (arbitrary upper bound)
But of course the while loop blocks MainLoop until $cmd does terminate.
Is there some way to read output handle asynchronously?
Or should I go with standard fork stuff?
The solution should work under win32, too.
For non-blocking read of a filehandle, take a look at Tk::fileevent.
Here's an example script how one can use a pipe, a forked process, and fileevent together:
use strict;
use IO::Pipe;
use Tk;
my $pipe = IO::Pipe->new;
if (!fork) { # Child XXX check for failed forks missing
$pipe->writer;
$pipe->autoflush(1);
for (1..10) {
print $pipe "something $_\n";
select undef, undef, undef, 0.2;
}
exit;
}
$pipe->reader;
my $mw = tkinit;
my $text;
$mw->Label(-textvariable => \$text)->pack;
$mw->Button(-text => "Button", -command => sub { warn "Still working!" })->pack;
$mw->fileevent($pipe, 'readable', sub {
if ($pipe->eof) {
warn "EOF reached, closing pipe...";
$mw->fileevent($pipe, 'readable', '');
return;
}
warn "pipe is readable...\n";
chomp(my $line = <$pipe>);
$text = $line;
});
MainLoop;
Forking may or may not work under Windows. Also one needs to be cautious when forking within Tk; you must make sure that only one of the two processes is doing X11/GUI stuff, otherwise bad things will happen (X11 errors, crashes...). A good approach is to fork before creating the Tk MainWindow.
I'm looking for a solution which allows me to return the values of a process executed in a child back to the parent process. Currently i try this but have no idea where to hook the return value:
use Proc::ProcessTable;
use POSIX qw(:signal_h :errno_h :sys_wait_h);
$SIG{CHLD} = \&REAPER;
for my $count (1..10) { # start a few demo childs
if (fork () == 0) {
&startChild;
exit 0;
}
}
do {
print "Working\n";
sleep 1;
} while (chkChildProcess());
sub startChild {
print "Starting Child $$\n";
system("date"); #==>Need to get the output of "date" back to parent
sleep 2 + rand 7;
print "End Child $$\n";
}
sub chkChildProcess {
for my $p (#{new Proc::ProcessTable->table}){
if ($p->ppid == $$){
$curPID{$$}=$p->pid;
return 1;
}
}
return undef;
}
sub REAPER {
my $pid;
$pid = waitpid(-1, &WNOHANG);
if ($pid == -1) {
# no child waiting. Ignore it.
} elsif (WIFEXITED($?)) {
print "Process $pid exited.\n";
} else {
print "False alarm on $pid.\n";
}
$SIG{CHLD} = \&REAPER; # in case of unreliable signals
}
Any help would be great.
The bg_eval and bg_qx methods of Forks::Super were made to solve this problem.
use Forks::Super 'bg_eval';
my #result;
for my $count (1 .. 10) {
$result[$count] = bg_eval {
my $date = `date`;
sleep 2 + rand 7;
return $date;
};
}
print "$result[$_]\n" for 1..10;
The block after bg_eval is run asynchronously in a background process. When the background process is finished, the variable $result[$count] will be populated with the result.
When you print $result[$_], one of two things will happen. If the background process associated with that variable is finished, it will contain its return value. If the background process is not finished, it will wait for the process to finish, and then make the return value available in that value.
It looks like you may want to use Parallel::ForkManager, returning the value from the child via the data_structure_reference parameter of the finish method to a run_on_finish callback in the parent.
To capture the output, the easiest way is to use IPC::System::Simple's capture or capturex.
you could use threads::shared instead of fork, create a shared variable, lock it and write into it. keep in mind that locking is reeeally slow!
See also this post on perlmonks on why locking the variable is necessary.
Is there any way to build some time counter that enable parts of a script to run as long it ticks? For example, I have the following code:
for my $i (0 .. $QUOTA-1) {
build_dyna_file($i);
comp_simu_exe;
bin2txt2errormap($i);
}
Theoretically I want to run this loop for 3 minutes, even if the loop instructions haven't finished yet, it should still break out of the loop after exactly 3 minutes.
Actually the program opens a time counter window that works in parallel to part of the script (each time I call it).
Additionally, the sub call 'comp_simu_exe' run an outside simulator (in the shell) that when time out ends - this process must also killed (not suppose to return after a while).
sub comp_simu_exe{
system("simulator --shell");
}
Is there any connection between the dead coming problem to the system function call ?
You can set an alarm that will break out of your code after a specified amount of seconds:
eval {
local $SIG{ ALRM } = sub { die "TIMEOUT" };
alarm 3 * 60;
for (my $i = 0 ; $i <$QUOTA ; $i++) {
build_dyna_file($i);
comp_simu_exe;
bin2txt2errormap($i);
}
alarm 0;
};
if ( $# && $# =~ m/TIMEOUT/ ) {
warn "operation timed out";
}
else {
# somebody else died
alarm 0;
die $#;
}
Or, if you really need the loop to run at least three times, no matter how long this might take:
eval {
my $t0 = time;
local $SIG{ ALRM } = sub { die "TIMEOUT" };
for (my $i = 0 ; $i <$QUOTA ; $i++) {
build_dyna_file($i);
comp_simu_exe;
bin2txt2errormap($i);
if ( $i == 3 ) {
my $time_remaining = 3 * 60 - time - $t0;
alarm $time_remaining if $time_remaining > 0;
}
}
alarm 0;
};
Here's a second answer that deals with the case of timing-out a second process. Use this case to start your external program and make sure that it doesn't take too long:
my $timeout = 180;
my $pid = fork;
if ( defined $pid ) {
if ( $pid ) {
# this is the parent process
local $SIG{ALRM} = sub { die "TIMEOUT" };
alarm 180;
# wait until child returns or timeout occurs
eval {
waitpid( $pid, 0 );
};
alarm 0;
if ( $# && $# =~ m/TIMEOUT/ ) {
# timeout, kill the child process
kill 9, $pid;
}
}
else {
# this is the child process
# this call will never return. Note the use of exec instead of system
exec "simulator --shell";
}
}
else {
die "Could not fork.";
}
The way you deal with timeouts like this in Perl is the alarm function. It will send the signal ALRM to your process after the number of seconds you pass into it. Be careful if the code you are trying to setup the timeout for calls sleep, as they do not mix well on many platforms. The basic structure looks like this:
#start a block eval to stop the die below from ending the program
eval {
#set the signal handler for the ALRM signal to die if it is run
#note, the local makes this signal handler local to this block
#eval only
local $SIG{ALRM} = sub { die "timeout\n" };
alarm $wait; #wait $wait seconds and then send the ALRM signal
#thing that could take a long time
alarm 0; #turn off the alarm (don't send the signal)
#any true value will do, if this line is reached the or do below won't run
1;
} or do {
#if we are in this block something bad happened, so we need to find out
#what it was by looking at the $# variable which holds the reason the
#the code above died
if ($# eq "timeout\n") {
#if $# is the timeout message then we timed out, take the
#proper clean up steps here
} else {
#we died for some other reason, possibly a syntax error or the code
#issued its own die. We should look at $# more carefully and determine
#the right course of action. For the purposes of this example I will
#assume that a message of "resource not available\n" is being sent by
#the thing that takes a long time and it is safe to continue the program.
#Any other message is unexpected.
#since we didn't timeout, but the code died, alarm 0 was never called
#so we need to call it now to prevent the default ALRM signal handler
#from running when the timeout is up
alarm 0;
if ($# eq "resource not available\n") {
warn $#;
} else {
die $#;
}
}
or written more compactly:
eval {
local $SIG{ALRM} = sub { die "timeout\n" };
alarm $wait; #wait $wait seconds and then send the ALRM signal
#thing that could take a long time
alarm 0;
1;
} or do {
die $# unless $# eq "timeout\n" or $# eq "resource not available\n";
alarm 0;
warn $#;
}
I have application that works using Perl's CGI::Fast.
Basically mainloop of the code is:
while (my $cgi = CGI::Fast->new() ) {
do_whatever( $cgi );
}
Now, I wanted to add ability to kill it, but without disrupting currently processed request. To do so, I added handling to SIGHUP. More or less along the lines:
my $sighupped = 0;
$SIG{ 'HUP' } = sub { $sighupped = 1; };
while (my $cgi = CGI::Fast->new() ) {
do_whatever( $cgi );
exit if $sighupped;
}
Which works really great when it comes to "not disturbing process while it handles user request". But, if the process is waiting for new request the sighup-based exit will not be executed until it will finally get some request and process it.
It there any workaround for this? What I would like to achieve it to make the script exit immediately if the HUP (or other signal, it can be changed) reaches it while waiting for request.
You could use the block version of the eval function and the alarm function to add a timeout to Fast::CGI. In the code below I have set a five second timeout. If it times out it will go back to the start of the loop which will check to see if $continue has been set to zero yet. If it hasn't then it we start a new CGI::Fast object. This means that a maximum of five seconds will go by after you send a HUP before the code will start to stop if it was waiting for a new CGI::Fast object (the timeout has no affect on the rest of the loop).
my $continue = 1;
$SIG{HUP} = sub { $continue = 0 };
while ($continue) {
my $cgi;
eval {
local $SIG{ALRM} = sub { die "timeout\n" };
alarm 5; #set an alarm for five seconds
$cgi = CGI::Fast->new;
alarm 0; #turn alarm off
};
if ($#) {
next if $# eq "timeout\n"; #we will see the HUP signal's change now
#died for a reason other than a timeout, so raise the error
die $#;
}
last unless defined $cgi; #CGI::Fast has asked us to exit
do_stuff($cgi);
}
#clean up
You could redeclare your SIGnals based on where the handler is.
If you're outside fast-cgi while loop or inside the fast-cgi polling and thus not in
the event loop terminate (basically the default). If you're inside the eventloop logic wait for logic to end and terminate.
Pardon my lack of an editor at the moment.
$inlogic=0;
$hupreq=0;
SIG{SIGHUP}={ exit unless($inlogic); $hupreq=1;}
while()
{
$inlogic=1;
function();
$inlogic=0;
exit if($hupreq);
}