AnyEvent::STOMP::Client + AnyEvent::ForkManger = Intermittent Error - perl

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} ],
);
}
);

Related

Mojo::UserAgent non-blocking vs blocking performance

I have the following code:
my $ua = Mojo::UserAgent->new ();
my #ids = qw(id1 id2 id3);
foreach (#ids) {
my $input = $_;
my $res = $ua->get('http://my_site/rest/id/'.$input.'.json' => sub {
my ($ua, $res) = #_;
print "$input =>" . $res->result->json('/net/id/desc'), "\n";
});
}
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
Why when I run the above code (non-blocking) does it take about 6 seconds while when running the code as blocking, i.e. inside the loop something like:
my $res = $ua->get('http://my_site/rest/id/'.$input.'.json');
print "$input =>" . $res->result->json('/net/id/desc'), "\n";
without the latest line it takes about 1 second?
Why is the blocking code faster than the non-blocking code?
The first thing to check when things happened. I couldn't get the same delay. Remember to try each way several times to spot outliers where there's a network hiccup. Note that the second argument to the non-blocking sub is a transaction object, normally written as $tx, where the response object is normally written res:
use Mojo::Util qw(steady_time);
say "Begin: " . steady_time();
END { say "End: " . steady_time() }
my $ua = Mojo::UserAgent->new ();
my #ids = qw(id1 id2 id3);
foreach (#ids) {
my $input = $_;
my $res = $ua->get(
$url =>
sub {
my ($ua, $tx) = #_;
print "Fetched\n";
}
);
}
One possibility is that keep-alive is holding an open connection. What happens if you turn that off?
my $res = $ua->get(
$url =>
{ Connection => 'close' }
sub {
my ($ua, $tx) = #_;
print "Fetched\n";
}
);
Here's a version that uses promises, which you'll want to get used to as more Mojo stuff moves to it:
use feature qw(signatures);
no warnings qw(experimental::signatures);
use Mojo::Promise;
use Mojo::Util qw(steady_time);
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
say "Begin: " . steady_time();
END { say "End: " . steady_time() }
my #ids = qw(id1 id2 id3);
my #gets = map {
$ua->get_p( 'http://www.perl.com' )->then(
sub ( $tx ) { say "Fetched: " . steady_time() },
sub { print "Error: #_" }
);
} #ids;
Mojo::Promise->all( #gets )->wait;

Perl: Issue with blessed object

I am creating a bot that connects to a Matrix server. For that I use Net::Async::Matrix.
The code:
#!/usr/bin/perl
use strict;
use warnings;
use Net::Async::Matrix;
use Net::Async::Matrix::Utils qw ( parse_formatted_message );
use IO::Async::Loop;
use Data::Dumper;
my $loop = IO::Async::Loop->new;
my $matrix = Net::Async::Matrix->new(
server => 'matrix.server.net',
on_error => sub {
my ( undef, $message ) = #_;
warn "error: $message\n";
},
);
$loop->add( $matrix );
$matrix->login(
user_id => '#bot:matrix.server.net',
password => 'password',
)->get;
my $room = $matrix->join_room( '#Lobby:matrix.server.net' )->get;
$room->configure(
on_message => sub {
my ( undef, $member, $content, $event ) = #_;
my $msg = parse_formatted_message( $content );
my $sendername = $member->displayname;
print Dumper $sendername;
&sendmsg("$sendername said: $msg");
},
);
my $stream = $matrix->start;
sub sendmsg {
my $input = shift;
if ($input) {
$room->send_message(
type => "m.text",
body => $input,
),
}
}
$loop->run;
Basically, I want the bot to echo what was said.
I get following output:
$VAR1 = 'm1ndgames'; Longpoll failed - encountered object 'm1ndgames
said: test', but neither allow_blessed, convert_blessed nor
allow_tags settings are enabled (or TO_JSON/FREEZE method missing) at
/usr/local/share/perl/5.24.1/Net/Async/Matrix.pm line 292.
and I don't understand it. When I enter a string like test into the body, it gets sent to the room.
parse_formatted_message returns a String::Tagged object. This class overloads concatenation so that "$sendername said: $msg" also returns a String::Tagged object. This object is passed to sendmsg which tries to serialize it into JSON, but it refuses to serialize objects.
Fix: Replace
my $msg = parse_formatted_message( $content );
with
my $msg = parse_formatted_message( $content )->str;
I'd guess that this is a quoting error. If you look at Net::Async::Matrix::Room:
sub send_message
{
my $self = shift;
my %args = ( #_ == 1 ) ? ( type => "m.text", body => shift ) : #_;
my $type = $args{msgtype} = delete $args{type} or
croak "Require a 'type' field";
$MSG_REQUIRED_FIELDS{$type} or
croak "Unrecognised message type '$type'";
foreach (#{ $MSG_REQUIRED_FIELDS{$type} } ) {
$args{$_} or croak "'$type' messages require a '$_' field";
}
if( defined( my $txn_id = $args{txn_id} ) ) {
$self->_do_PUT_json( "/send/m.room.message/$txn_id", \%args )
->then_done()
}
else {
$self->_do_POST_json( "/send/m.room.message", \%args )
->then_done()
}
}
The type you sent is handled by this sub, and then the actual message gets handed off to _do_POST_json in Net::Async::Matrix.
But you've sent a string containing a :.
So I think what's happening is it's encoding like this:
use JSON;
use Data::Dumper;
my $json = encode_json ( {body => "m1ndgames: said test"});
print Dumper $json;
But the response that's coming back, at line 292 which is:
if( length $content and $content ne q("") ) {
eval {
$content = decode_json( $content );
1;
} or
return Future->fail( "Unable to parse JSON response $content" );
return Future->done( $content, $response );
}
So I think is what is happening is the remote server is sending you a broken error code, and the module isn't handling it properly - it's expecting JSON but it isn't actually getting it.
My best guess would be - try dropping the : out of your message, because I would guess there's some bad quoting happening. But without seeing the code on the server side, I can't quite tell.

Sequentioal requests with Mojo::IOLoop::Delay

I need to get list of URLs in non-blocking mode, but not in parallel. It should be sequential requests one by one. How can I realize that?
I cannot find examples. Documentation and articles highlight only parallel execution.
Now my code looks like the following (simplified):
my $delay = Mojo::IOLoop::Delay->new;
$delay->steps(
sub {
build_report();
say "done";
}
);
sub parse_data {
...;
my $url = shift #urls;
my $end = $delay->begin;
$ua->get( $url => \&parse_data );
$end->();
}
my $end = $delay->begin;
$ua->get( $url => \&parse_data );
$end->();
$delay->wait;
I want to avoid multiple closures by using Mojo::IOLoop::Delay.
I have just started looking at Mojo::IOLoop myself for a project and I'm not au fait with it yet. But I think the simplest way is to build an array of closures and pass it to $delay->steps.
use strict;
use warnings 'all';
use feature 'say';
use Mojo;
use constant URL => 'http://stackoverflow.com/questions/tagged/perl';
STDOUT->autoflush;
my $ua = Mojo::UserAgent->new;
my $delay = Mojo::IOLoop::Delay->new;
my #urls = ( URL ) x 10;
my #steps = map {
my $url = $_;
sub {
my $end = $delay->begin;
$ua->get( $url => sub {
my ( $ua, $txn ) = #_;
$end->();
if ( my $err = $txn->error ) {
say $err->message;
}
else {
my $res = $txn->success;
say "#{$res}{qw/ code message /}";
}
});
}
} #urls;
$delay->steps( #steps, sub { say 'Done' } );
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
output
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
Done
200 OK
Note that Done is printed before the status line of the final get, because I have called $end->() immediately the callback arrives, assuming that any handling of the response doesn't need to be synchronised
If you don't want that then just move $end->() to the end of the callback. Then the delay will wait until the output has been generated before sending another request
Sequential requests very easy to implement.
Web server
#!/usr/bin/perl
use Mojo::Base -strict;
use Mojolicious::Lite;
get '/' => sub {
my $c = shift->render_later;
$c->delay(sub {
my $delay = shift;
$c->ua->get('https://google.ru' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the google.ru
$c->ua->get('https://twitter.com' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the twitter.com
$c->render(text => 'Done');
});
};
app->start;
Script
#!/usr/bin/perl
use Mojo::Base -strict;
use Mojo::IOLoop;
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
Mojo::IOLoop->delay(sub {
my $delay = shift;
$ua->get('https://www.google.ru' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the google.ru
$ua->get('https://twitter.com' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the twitter.com
warn 'DONE';
})->wait;
Script (dynamic requests)
Example here

How to do asynchronous www-mechanize using anyevent

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;

How to clean-up HTTP::Async if still in use

I am using Perl library HTTP::Async as follows:
use strict;
use warnings;
use HTTP::Async;
use Time::HiRes;
...
my $async = HTTP::Async->new( ... );
my $request = HTTP::Request->new( GET => $url );
my $start = [Time::HiRes::gettimeofday()];
my $id = $async->add($request);
my $response = undef;
while (!$response) {
$response = $async->wait_for_next_response(1);
last if Time::HiRes::tv_interval($start) > TIME_OUT;
}
...
When while loop timeout and script ends, I experience the the following error message:
HTTP::Async object destroyed but still in use at script.pl line 0
HTTP::Async INTERNAL ERROR: 'id_opts' not empty at script.pl line 0
What are my options? How can I "clean-up" HTTP::Async object if still in use, but not needed anymore?
I would suggest that you remove incomplete requests, but the module does not provide any interface to do so.
Option 1: Add removal functionality.
Add the following to your script:
BEGIN {
require HTTP::Async;
package HTTP::Async;
if (!defined(&remove)) {
*remove = sub {
my ($self, $id) = #_;
my $hashref = $self->{in_progress}{$id}
or return undef;
my $s = $hashref->{handle};
$self->_io_select->remove($s);
delete $self->{fileno_to_id}{ $s->fileno };
delete $self->{in_progress}{$id};
delete $self->{id_opts}{$id};
return $hashref->{request};
};
}
if (!defined(&remove_all)) {
*remove_all = sub {
my ($self) = #_;
return map $self->remove($_), keys %{ $self->{in_progress} };
};
}
}
You should contact the author and see if he can add this feature. $id is the value returned by add.
Option 2: Silence all warnings from the destructor.
If you're ok with not servicing all the requests, there's no harm in silencing the warnings. You can do so as follows:
use Sub::ScopeFinalizer qw( scope_finalizer );
my $async = ...;
my $anchor = scope_finalizer {
local $SIG{__WARN__} = sub { };
$async = undef;
};
...
Note that this will silence all warnings that occur during the object's destruction, so I don't like this as much.
It's not too hard to subclass HTTP::Async for a more general solution. I use this to be able to abort all pending requests:
package HTTP::Async::WithFlush;
use strict;
use warnings;
use base 'HTTP::Async';
use Time::HiRes qw(time);
sub _flush_to_send {
my $self = shift;
for my $request (#{ $self->{to_send} }) {
delete $self->{id_opts}->{$request->[1]};
}
$self->{to_send} = [];
}
sub _flush_in_progress {
my $self = shift;
# cause all transfers to time out
for my $id (keys %{ $self->{in_progress} }) {
$self->{in_progress}->{$id}->{finish_by} = time - 1;
}
$self->_process_in_progress;
}
sub _flush_to_return {
my $self = shift;
while($self->_next_response(-1)) { }
}
sub flush_pending_requests {
my $self = shift;
$self->_flush_to_send;
$self->_flush_in_progress;
$self->_flush_to_return;
return;
}
1;
This is (maybe) easier on using the module internals than the code by #ikegami.