I've been doing a fair amount of research on the topic and while there are some questions out there that relate, I'm really having a hard time understanding how to properly do async programming using AnyEvent and www-mechanize. I'm trying to stick with mechanize because it has a clean interface and has functions built-in that I'm expecting to do: (like get all images of a site etc). If there is no reliable/good way to do what I want, then I'll start looking at AnyEvent::HTTP but I figure I'd ask first before moving in that direction.
I'm a newbie to AnyEvent programming but have done a fair amount of perl and javascript / jquery async calls with callbacks before. These make a lot of sense to me but it's just not clicking for me with AnyEvent + Mech.
Here is the code I'm working on that pulls URLs from an upstream queue. give the URL, I want to one get that says pulls in all the images on a page, and then async. grabs all the images.
So pseudo-code would look something like this:
grab url from queue
get page
get all img url links
do many async calls on the img urls (store the imgs for example in a backend)
I've read, I cannot (after researching errors) block in an AnyEvent callback. How do I structure my program to do the async calls without blocking?
AE events can only be processed when AE-aware functions block, so I'm using LWP::Protocol::AnyEvent::http. It replaces the normal HTTP backend for LWP (Net:HTTP) with AnyEvent::HTTP, which is AE-aware.
The worker gets created like:
my Worker->new(upstream_job_url => "tcp://127.0.0.1:5555', run_on_create => 1);
Async part is sub _recv_msg which calls _proc_msg.
I already have an AnyEvent loop watching the ZeroMQ socket as per the ZeroMQ perl binding docs...
Any help much appreciated!
Code:
package Worker;
use 5.12.0;
use Moose;
use AnyEvent;
use LWP::Protocol::AnyEvent::http;
use ZMQ::LibZMQ3;
use ZMQ::Constants qw/ZMQ_PUSH ZMQ_PULL ZMQ_POLLIN ZMQ_FD/;
use JSON;
use WWW::Mechanize;
use Carp;
use Coro;
has 'max_children' => (
is => 'rw',
isa => 'Int',
required => 1,
default => sub { 0 }
);
has 'upstream_job_url' => (
is => 'rw',
isa => 'URI',
required => 1,
);
has ['uri','sink_url'] => (
is => 'rw',
isa => 'URI',
required => 0,
);
has 'run_on_create' => (
is => 'rw',
isa => 'Bool',
required => 1,
default => sub { 1 }
);
has '_receiver' => (
is => 'rw',
isa => 'ZMQ::LibZMQ3::Socket',
required => 0
);
sub BUILD {
my $self = shift;
$self->start if $self->run_on_create;
}
sub start
{
my $self = shift;
$self->_init_zmq();
my $fh = zmq_getsockopt( $self->_receiver, ZMQ_FD );
my $w; $w = AnyEvent->io( fh => $fh, poll => "r", cb => sub { $self->_recv_msg } );
AnyEvent->condvar->recv;
}
sub _init_zmq
{
my $self = shift;
my $c = zmq_init() or die "zmq_init: $!\n";
my $recv = zmq_socket($c, ZMQ_PULL) or die "zmq_socket: $!\n";
if( zmq_connect($recv, $self->upstream_job_url) != 0 ) {
croak "zmq_connect: $!\n";
}
$self->_receiver($recv);
}
sub _recv_msg
{
my $self = shift;
while(my $message = zmq_msg_data(zmq_recvmsg($self->_receiver)) ) {
my $msg = JSON::from_json($message, {utf8 => 1});
$self->uri(URI->new($msg->{url}));
$self->_proc_msg;
}
}
sub _proc_msg
{
my $self = shift;
my $c = async {
my $ua = WWW::Mechanize->new;
$ua->protocols_allowed(['http']);
print "$$ processing " . $self->uri->as_string . "... ";
$ua->get($self->uri->as_string);
if ($ua->success()) {
say $ua->status . " OK";
} else {
say $ua->status . " NOT OK";
}
};
$c->join;
}
1;
As you can see, I was trying Coro in the _proc_msg, I've tried just doing mech calls but get an error
AnyEvent::CondVar: recursive blocking wait attempted at lib/Worker.pm line 91.
Because $mech is still blocking in the callback. I'm not sure how to do the mech calls in my callback properly.
At ikegami's request, i've added the driver program that sends the urls. For test purposes, I have it just reading a RSS feed, and sending the links to the workers to attempt to process. I was curious just about basic structure of anyevent with the callbacks but I'm more than happy just to get help on the program in general. Here is the driver code:
#!/usr/local/bin/perl
use strict;
use warnings;
use v5.12.0;
use lib './lib';
use Config::General;
use Getopt::Long;
use Carp;
use AnyEvent;
use AnyEvent::Feed;
use Parallel::ForkManager;
use ZMQ::LibZMQ3;
use ZMQ::Constants qw(ZMQ_PUSH ZMQ_PULL);
use Worker;
# Debug
use Data::Dumper;
$Data::Dumper::Deparse = 1;
my $config_file = "feeds.cfg";
GetOptions(
"--config|c" => \$config_file,
"--help|h" => sub { usage(); exit(0); }
);
sub usage()
{
say "TODO";
}
$SIG{INT} = sub { croak; }; $SIG{TERM} = sub { croak; };
$SIG{CHLD} = 'IGNORE';
my $conf = Config::General->new($config_file) or croak "Couldn't open config file '$config_file' $!\n";
my %config = $conf->getall();
my #readers = ();
my #feeds = load_feeds(\%config);
my $mgr = Parallel::ForkManager->new( $config{'max_download_children'} ) or croak "Can't create fork manager: $!\n";
my $context = zmq_init() or croak "zmq_init: $!\n";
my $sender = zmq_socket($context, ZMQ_PUSH) or die "zmq_socket: $!\n";
foreach my $feed_cfg (#feeds) {
my $reader = AnyEvent::Feed->new(url => delete $feed_cfg->{url}, %$feed_cfg);
push(#readers, $reader); # save, don't go out of scope
}
# Fork Downloader children. These processes will look for incoming data
# in the img_queue and download the images, storing them in nosql
for ( 1 .. $config{'max_download_children'} ) {
my $pid = $mgr->start;
if (!$pid) {
# Child
my $worker = Worker->new({
upstream_job_url => URI->new('tcp://127.0.0.1:5555')
});
$mgr->finish;
say "$$ exiting.";
exit(0);
} else {
# Parent
say "[forked child $pid] my pid is $$";
}
}
if (zmq_bind($sender, 'tcp://127.0.0.1:5555') < 0) {
croak "zmq_bind: $!\n";
}
# Event loop
AnyEvent->condvar->recv;
sub load_feeds
{
my $conf = shift;
my #feeds = ();
foreach my $feed ( keys %{$conf->{'feeds'}} ) {
my $feed_ref = $conf->{'feeds'};
$feed_ref->{$feed}->{'name'} = $feed;
$feed_ref->{$feed}->{'on_fetch'} = \&fetch_feed_cb;
push(#feeds, $feed_ref->{$feed});
}
return #feeds;
}
sub fetch_feed_cb
{
my ($feed_reader, $new_entries, $feed, $error) = #_;
if (defined $error) {
say "Error fetching feed: $error";
return;
}
say "$$ checking for new feeds";
for (#$new_entries) {
my ($hash, $entry) = #$_;
say "$$ sending " . $entry->link;
zmq_send($sender, JSON::to_json( { url => $entry->link }, { pretty => 1, utf8 => 1 } ));
}
}
Here is a sample run:
[forked child 40790] my pid is 40789
[forked child 40791] my pid is 40789
[forked child 40792] my pid is 40789
40789 checking for new feeds
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/f5nNM3zYBt0/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/Ay9V5pIpFBA/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/5XCVvt75ppU/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/mWprjBD3UhM/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/NngMs9pCQew/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/wiUsvafLGFU/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/QMp6gnZpFcA/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/kqUb_rpU5dE/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/tHItKqKhGXg/
40789 sending http://feedproxy.google.com/~r/PerlNews/~3/7LleQbVnPmE/
FATAL: $Coro::IDLE blocked itself - did you try to block inside an event loop callback? Caught at lib/Worker.pm line 99.
FATAL: $Coro::IDLE blocked itself - did you try to block inside an event loop callback? Caught at lib/Worker.pm line 99.
FATAL: $Coro::IDLE blocked itself - did you try to block inside an event loop callback? Caught at lib/Worker.pm line 99.
40791 processing http://feedproxy.google.com/~r/PerlNews/~3/Ay9V5pIpFBA/...
40790 processing http://feedproxy.google.com/~r/PerlNews/~3/f5nNM3zYBt0/...
40792 processing http://feedproxy.google.com/~r/PerlNews/~3/5XCVvt75ppU/... ^C at /usr/local/perls/perl5162/lib/perl5/site_perl/darwin-thread-multi-2level/AnyEvent/Loop.pm line 231.
If I don't explicitly do a 'use Coro;' in Worker.pm, the coro FATAL errors don't show. I don't know how async was working before without further runtime errors.
Sample config file (feeds.cfg):
max_download_children = 3
<feeds>
<feed1>
url="http://feeds.feedburner.com/PerlNews?format=xml"
interval=60
</feed1>
</feeds>
So I spent a little more time with this today. So the error of my ways doing a $c->join. I shouldn't do that since I can't block in the callback. Coro will schedule the async block and it will be done when it's done. The only thing I need to make sure to do is to somehow know when all the asyncs are done which I think I can figure out. Now the tricky part is trying to figure out this little piece of mystery:
sub _recv_msg
{
my $self = shift;
while(my $message = zmq_msg_data(zmq_recvmsg($self->_receiver)) ) {
my $msg = JSON::from_json($message, {utf8 => 1});
$self->uri(URI->new($msg->{url}));
$self->_proc_msg;
}
}
This while loop causes my async { } threads in _proc_msg to NOT RUN. Remove the while loop and just handle the first msg and the coros run. Leave the while loop in place and they never will get run. Strange to me, haven't figured out why yet.
Further updates:
zmq_msg_recv was blocking. Also, zmq_send in the parent can block. Have to use ZMQ_NOBLOCK.
I split the worker and main into separate programs entirely.
you could use https://metacpan.org/pod/AnyEvent::HTTP::LWP::UserAgent for async calls.
use AnyEvent::HTTP::LWP::UserAgent;
use AnyEvent;
my $ua = AnyEvent::HTTP::LWP::UserAgent->new;
my #urls = (...);
my $cv = AE::cv;
$cv->begin;
foreach my $url (#urls) {
$cv->begin;
$ua->get_async($url)->cb(sub {
my $r = shift->recv;
print "url $url, content " . $r->content . "\n";
$cv->end;
});
}
$cv->end;
$cv->recv;
Related
I use Mojo::IOLoop to talk to a port to send commands to a digital receiver.
Because the receiver can only open the port once, but the code talking to it sits behind a web interface and a web server, there is a risk that commands get sent concurrently - so the digital receiver loses them, and things go wrong.
So - how can I ensure that some piece of code never gets run concurrently?
I was trying to use file locks like this:
sub enqueue {
my $self = shift;
my ($cmd, $promise) = #_;
while (!(flock DATA, LOCK_EX|LOCK_NB)) { sleep(0.25) }
my (#r);
my $id = Mojo::IOLoop->client({ address => '192.168.1.204',
port => 23, timeout => 13 }
=> sub ($loop, $err, $stream) {
return $promise->reject('could not open stream') unless $stream;
$stream->on(error => sub ($stream, $err) {
flock(DATA, LOCK_UN);
$stream->close;
$promise->reject($err);
});
$stream->on(read => sub ($stream, $bytes) {
$bytes =~ s/\r\n$//;
push #r, split /\r\n/, $bytes;
# stream done is a function that checks that
# the appropriate number of lines got sent back
if (stream_done(#r)) {
flock(DATA, LOCK_UN);
$promise->resolve(\#r);
$stream->close;
};
});
$stream->on(timeout => sub ($stream) {
flock(DATA, LOCK_UN);
$stream->close;
$promise->reject('timeout') if ($err);
});
$stream->write($cmd);
});
}
I'm not sure if I understand correctly what you want to do as I am not familiar with Mojo. But if you just don't want to execute a function two times create a lock file and delete if when it is finished:
use File::Slurp;
sub my_method_to_execute_only_once {
my $tmp = 'temp.txt';
return if (is_method_running($tmp));
# DO YOUR STUFF
unlink $tmp;
}
sub is_method_running{
my ($filename) = #_;
if (-e $tmp){
return 1;
}
write_file($tmp, "running");
return 0;
}
I'm trying to write a process that listens to ActiveMQ and based on the message, goes out and grabs data from a webservice, does some processing and then puts the process data to another webservice. (REST/JSON)
The module below works fine until one of the wonky webservices I talk to returns an error. I've tried many things to catch the error but to no avail, yet. Once the webservice error happens though I get the following message:
unhandled callback exception on event (MESSAGE,
AnyEvent::STOMP::Client=HASH(0x3ad5e48), HASH(0x3a6bbb0)
{"action":"created","data":{"id":40578737,"type":"alert","who":null},"guid":"ADCCEE0C-73A7-11E6-8084-74B346D1CA67","hostname":"myserver","pid":48632}):
$fork_manager->start() should be called within the manager process
OK, I conceptually understand that child process is trying to start another process and that fork manager is saying that is a no no. But given the module below, what is the proper way to start a new process to handle the long running processing. Or why is an child process dying causing this exception and how can I prevent this
Here's the module (stripped down)
package consumer;
use AnyEvent::ForkManager;
use AnyEvent::STOMP::Client;
use JSON;
use Data::Dumper;
use v5.18;
use Moose;
sub run {
my $self = shift;
my $pm = AnyEvent::ForkManager->new(max_workers => 20);
my $stomp = AnyEvent::STOMP::Client->new();
$stomp->connect();
$stomp->on_connected(sub {
my $stomp = shift;
$stomp->subscribe('/topic/test');
say "Connected to STOMP";
});
$pm->on_start(sub {
my ($pm,$pid,#params) = #_;
say "Starting $pid worker";
});
$pm->on_finish(sub {
my ($pm, $pid,#params) = #_;
say "Finished $pid worker";
});
$pm->on_error(sub {
say Dumper(\#_);
});
$stomp->on_message(sub {
my ($stomp, $header, $body) = #_;
my $href = decode_json $body;
$pm->start(cb => sub {
my ($pm, #params) = #_;
$self->process(#params);
},
args => [ $href->{id}, $href->{data}->{type}, $href->{data}->{who} ],
);
});
my $cv = AnyEvent->condvar;
$cv->recv;
}
sub process {
say "Processing ".Dumper(\#_);
sleep 5;
if ( int(rand(10)) < 5 ) {
die "OOPS"; # this triggers the error message above
}
say "Done Processing $_[1]";
}
1;
Heres the driver for the module above:
#!/usr/bin/env perl
use v5.18;
use lib '.';
use consumer;
my $c = consumer->new();
$c->run;
Finally a traffic generator that you can use to see this in action:
#!/usr/bin/env perl
use lib '../lib';
use lib '../../lib';
use v5.18;
use Data::Dumper;
use JSON;
use Net::STOMP::Client;
$ENV{'scot_mode'} = "testing";
my $stomp = Net::STOMP::Client->new(
host => "127.0.0.1",
port => 61613
);
$stomp->connect();
for (my $i = 1; $i < 1000000; $i++) {
my $href = {
id => $i,
type => "event",
what => "foo",
};
my $json = encode_json $href;
say "Sending ".Dumper($href);
$stomp->send(
destination => "/topic/test",
body => $json,
);
}
$stomp->disconnect();
I was able to solve this by using Try::Catch and wrapping the call to self->process with a try catch like this:
$stomp->on_message(sub {
my ($stomp, $header, $body) = #_;
my $href = decode_json $body;
$pm->start(cb => sub {
my ($pm, #params) = #_;
try {
$self->process(#params);
}
catch {
# error handling stuff
};
},
args => [ $href->{id}, $href->{data}->{type}, $href->{data}->{who} ],
);
}
);
In a Mojolicious::Lite app I have a route that I want to kill the server and redirect to another site. Here is the snippet.
my $me = $$;
get '/kill' => sub {
my $self = shift;
$self->res->code(301);
$self->redirect_to('http://www.google.com');
$self->app->log->debug("Goodbye, $name.");
# I need this function to return so I delay the kill a little.
system("(sleep 1; kill $me)&");
};
This code does what I want, but it doesn't feel right. I have tried $self->app->stop but that is not available.
Is there a proper technique I should be using to get access to the server?
Update 2021:
This answer was referred to recently in an IRC discussion, so an update is warranted. The response below was a mechanism that I had used in a very specific case. While it may still be useful in rare cases, the more correct manner of stopping a service would be
https://docs.mojolicious.org/Mojo/IOLoop#stop_gracefully
or https://docs.mojolicious.org/Mojo/Server/Daemon#SIGNALS for a single-process server or https://docs.mojolicious.org/Mojo/Server/Prefork#MANAGER-SIGNALS for preforking
Original:
There are several ways to do this, of course.
Probably the best, is to simply attach a finish handler to the transaction:
#!/usr/bin/env perl
use Mojolicious::Lite;
get '/kill' => sub {
my $c = shift;
$c->redirect_to('http://google.com');
$c->tx->on( finish => sub { exit } );
};
app->start;
The method most like your example would be to setup a Mojo::IOLoop timer which would wait a few seconds and exit.
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mojo::IOLoop;
get '/kill' => sub {
my $c = shift;
$c->redirect_to('http://google.com');
my $loop = Mojo::IOLoop->singleton;
$loop->timer( 1 => sub { exit } );
$loop->start unless $loop->is_running; # portability
};
app->start;
Joel mentioned Mojo::IOLoop, so here's what I've used for a simple Mojo Lite throwaway app:
get '/shutdown' => sub ($c) {
$c->render(text => "Shutting down" );
$c->tx->on( finish => sub { Mojo::IOLoop->stop_gracefully } );
};
Sending signals also works since this is a single process program:
get '/shutdown' => sub ($c) {
$c->render(text => "Shutting down" );
$c->tx->on( finish => sub { kill 'TERM', $$ } );
};
I'm working on creating a local service to listen on localhost and provide a basic call and response type interface. What I'd like to start with is a baby server that you can connect to over telnet and echoes what it receives.
I've heard AnyEvent is great for this, but the documentation for AnyEvent::Socket does not give a very good example how to do this. I'd like to build this with AnyEvent, AnyEvent::Socket and AnyEvent::Handle.
Right now the little server code looks like this:
#!/usr/bin/env perl
use AnyEvent;
use AnyEvent::Handle;
use AnyEvent::Socket;
my $cv = AnyEvent->condvar;
my $host = '127.0.0.1';
my $port = 44244;
tcp_server($host, $port, sub {
my($fh) = #_;
my $cv = AnyEvent->condvar;
my $handle;
$handle = AnyEvent::Handle->new(
fh => $fh,
poll => "r",
on_read => sub {
my($self) = #_;
print "Received: " . $self->rbuf . "\n";
$cv->send;
}
);
$cv->recv;
});
print "Listening on $host\n";
$cv->wait;
This doesn't work and also if I telnet to localhost:44244 I get this:
EV: error in callback (ignoring): AnyEvent::CondVar:
recursive blocking wait attempted at server.pl line 29.
I think if I understand how to make a small single threaded server that I can connect to over telnet and prints out whatever its given and then waits for more input, I could take it a lot further from there. Any ideas?
You're blocking inside a callback. That's not allowed. There are a few ways to handle this. My preference is to launch a Coro thread from within the tcp_server callback. But without Coro, something like this might be what you're looking for:
#!/usr/bin/env perl5.16.2
use AnyEvent;
use AnyEvent::Handle;
use AnyEvent::Socket;
my $cv = AE::cv;
my $host = '127.0.0.1';
my $port = 44244;
my %connections;
tcp_server(
$host, $port, sub {
my ($fh) = #_;
print "Connected...\n";
my $handle;
$handle = AnyEvent::Handle->new(
fh => $fh,
poll => 'r',
on_read => sub {
my ($self) = #_;
print "Received: " . $self->rbuf . "\n";
},
on_eof => sub {
my ($hdl) = #_;
$hdl->destroy();
},
);
$connections{$handle} = $handle; # keep it alive.
return;
});
print "Listening on $host\n";
$cv->recv;
Note that I'm only waiting on one condvar. And I'm storing the handles to keep the AnyEvent::Handle objects alive longer. Work to clean up the $self->rbuf is left as an excersise for the reader :-)
Question cross-posted, answer, too :-)
I have heard good things about AnyEvent as well, but have not used it. I wrote a small nonblocking server in the past using IO::Select. There is an example in the documentation for that module (I've added a few lines):
use IO::Select;
use IO::Socket;
$lsn = new IO::Socket::INET(Listen => 1, LocalPort => 8080);
$sel = new IO::Select( $lsn );
while(#ready = $sel->can_read) {
foreach $fh (#ready) {
if($fh == $lsn) {
# Create a new socket
$new = $lsn->accept;
$sel->add($new);
}
else {
# Process socket
my $input = <$fh>;
print $fh "Hello there. You said: $input\n";
# Maybe we have finished with the socket
$sel->remove($fh);
$fh->close;
}
}
}
I'm not sure what your condvar is trying to trigger there. Use it to send state, like:
#!/usr/bin/env perl
use AnyEvent;
use AnyEvent::Handle;
use AnyEvent::Socket;
my $host = '127.0.0.1';
my $port = 44244;
my $exit = AnyEvent->condvar;
tcp_server($host, $port, sub {
my($fh) = #_;
my $handle; $handle = AnyEvent::Handle->new(
fh => $fh,
poll => "r",
on_read => sub {
my($self) = #_;
print "Received: " . $self->rbuf . "\n";
if ($self->rbuf eq 'exit') {
$exit->send;
}
}
);
});
print "Listening on $host\n";
$exit->recv;
Have the following in my module's BEGIN section:
use sigtrap qw(handler shutdown normal-signals);
use sigtrap qw(die untrapped normal-signals stack-trace any error-signals);
But when sigtrap catches INT,etc.. what I get in my shutdown sub only contains the trap and not the object handle. No $self.
sub shutdown {
my $sig = shift || 'Nothing';
print "Got signal: $sig\n";
exit;
}
simply returns
Got signal: INT
My DESTROY get's called right on time after this and has access to the object handle, but because I didn't have access to the handle in my shutdown, I couldn't store it and have no idea what the signal was.
I need to know what trap I got so my DESTROY method can log what caused the shutdown.
Perhaps sigtrap isn't the best choice here. Opinions welcome.
I checked sigtrap, it's not specifically an OO module, if you want to use it as one, you may need to use a closure instead of an object method.
So you might define your class like so:
package SigHandler;
sub new {
my $class = shift;
return bless { #_ }, $class;
}
sub on_signal_int {
my $self = shift;
...
}
sub get_handler {
my $self = shift;
my #other_args = shift;
...
return sub {
my $sig = shift;
if ( $sig == INT ) {
return $self->on_signal_int();
}
};
}
And then call it like so:
use handler => SigHandler->new->get_handler, 'normal-signals';
Perl's signal handlers, including those set with sigtrap are program level, and not object level. So when perl calls the handler, it does not have any object to pass you.
If you want to clean up a bunch of objects when you receive a signal, you will need to code your module to keep track of the objects it has created. Then when you receive a signal, you can go through those objects and perform any destroy methods.
Something like this should get you started:
{package Test;
use Scalar::Util 'weaken';
use sigtrap handler => \&cleanup, 'normal-signals';
my %objects;
sub new {
my ($class, $msg) = #_;
my $self = [$msg];
bless $self, $class;
weaken($objects{$self} = $self); # prevent memory leak
$self
}
sub cleanup {
my ($sig) = #_;
say "cleanup on $sig";
defined and $_->DESTROY for values %objects;
exit;
}
sub DESTROY {
my ($self) = #_;
if (#$self) {
say "DESTROY $self #$self";
#$self = ();
delete $objects{$self}
}
}
}
{my $obj1 = Test->new('out of scope')}
my $obj2 = Test->new('in scope');
1 while 1;
And when run:
$ perl so.pl
DESTROY Test=ARRAY(0x899150) out of scope
^Ccleanup on INT
DESTROY Test=ARRAY(0x824810) in scope
Thank you for your insights, but I ended up cheating by using global to track it.
All the exports and common stuff removed for brevity
Package Blah;
our $SIG_CAUGHT = '';
BEGIN {
use sigtrap qw(handler shutdown normal-signals);
use sigtrap qw(die untrapped normal-signals stack-trace any error-signals);
}
sub shutdown {
$SIG_CAUGHT = shift;
exit;
}
sub DESTROY {
my $self = shift;
my $message = 'Daemon shutting down';
$message .= '. Caught signal: SIG' . $SIG_CAUGHT if ( $SIG_CAUGHT ne '' );
$message .= ' with error: ' . $! if $!;
$self->logger({severity => 5, message => $message});
$self->{_dbh} = undef;
}
1;
Tested.. correctly handles INT,KILL,DIE, propagating errors when fatal.
One nice side effect is triggering INT is now one keystroke.
Had the issue in the past where I repeatedly had to Control-C my programs to get them to go down.