From 54f402a124bc93a6eab5bafefff0b2f057670f58 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 5 Jan 2012 02:54:20 +0000 Subject: [PATCH 001/165] Fix memory leaks --- Changes | 3 +++ lib/AnyEvent/RabbitMQ/Channel.pm | 1 + 2 files changed, 4 insertions(+) diff --git a/Changes b/Changes index 6ec4a3b..2449377 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Stop leaking all RabbitMQ messages recieved back inside + a closure. + - Allow multiple clients to have independent connections to RabbitMQ, as long as they all use the same spec file. diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index d800768..2d20666 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -654,6 +654,7 @@ sub _push_read_header_and_body { $self->{_content_queue}->get($next_frame); } else { + undef $next_frame; $frame->payload($body_payload); $response->{body} = $frame; $cb->($response); From a14951f58a607e310ea48f3933a7f78fd9717bf0 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 5 Jan 2012 12:14:14 +0000 Subject: [PATCH 002/165] Fix the ->new_channel ->close race --- Changes | 7 +++++++ lib/AnyEvent/RabbitMQ/Channel.pm | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 2449377..e4325f9 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,12 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Fix a race condition stopping connections from closing properly. + If you ask to open a channel, and then immediately try to close + the connection then the not yet open channel would never remove + itself from the associated connection, resulting in the connection + never being terminated (as there were still channels associated with + it). + - Stop leaking all RabbitMQ messages recieved back inside a closure. diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 2d20666..95419ab 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -42,7 +42,9 @@ sub open { $self->{_is_active} = 1; $args{on_success}->(); }, - $args{on_failure}, + sub { + $args{on_failure}->(@_); + }, $self->{id}, ); @@ -55,7 +57,16 @@ sub close { or return; my %args = $connection->_set_cbs(@_); - return $self if !$self->{_is_open}; + # Ensure to remove this channel from the connection even if we're not + # fully open to ensure $rf->close works always. + # FIXME - We can end up racing here so the server thinks the channel is + # open, but we've closed it - a more elegant fix would be to mark that + # the channel is opening, and wait for it to open before closing it + if (!$self->{_is_open}) { + $self->{connection}->delete_channel($self->{id}); + $args{on_success}->($self); + return $self; + } return $self->_close(%args) if 0 == scalar keys %{$self->{_consumer_cbs}}; From 8cc40abc22ae0fd1e1b8aec01a0f68f69f90ba61 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 16 Feb 2012 11:25:37 +0000 Subject: [PATCH 003/165] Fix S=>C Connection::Close --- Changes | 4 ++++ lib/AnyEvent/RabbitMQ.pm | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index e4325f9..20649b9 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Fix that fact that the AMQP server can ask us to close the + connection - we should respond with CloseOk and then + not say anything further otherwise we get a SIGPIPE. + - Fix a race condition stopping connections from closing properly. If you ask to open a channel, and then immediately try to close the connection then the not yet open channel would never remove diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index b00e421..ed7d78a 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -188,11 +188,10 @@ sub _check_close_and_clean { my ($frame, $close_cb,) = @_; return 1 if !$frame->isa('Net::AMQP::Frame::Method'); - my $method_frame = $frame->method_frame; - return 1 if !$method_frame->isa('Net::AMQP::Protocol::Connection::Close'); - - $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()); + return 1 if !$method_frame->isa('Net::AMQP::Protocol::Connection::Close') && !$method_frame->isa('Net::AMQP::Protocol::Connection::CloseOk'); + $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()) + if $method_frame->isa('Net::AMQP::Protocol::Connection::Close'); $self->{_channels} = {}; $self->{_is_open} = 0; $self->_disconnect(); From 51cd3a08f57adfb9def286e1d799581532d94917 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 31 May 2012 20:52:43 +0100 Subject: [PATCH 004/165] Add MYMETA --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e2a4cfe..5a5ef1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +MYMETA.* cover_db META.yml Makefile From a0bc123a09b585b960a6c466ef51d44effc0a121 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 21 Aug 2012 15:20:38 +0200 Subject: [PATCH 005/165] revert 8cc40abc22ae0fd1e1b8aec01a0f68f69f90ba61 which breaks tests --- Changes | 9 +++------ lib/AnyEvent/RabbitMQ.pm | 7 ++++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index 20649b9..4acad69 100644 --- a/Changes +++ b/Changes @@ -1,15 +1,12 @@ Revision history for Perl extension AnyEvent::RabbitMQ - - Fix that fact that the AMQP server can ask us to close the - connection - we should respond with CloseOk and then - not say anything further otherwise we get a SIGPIPE. - +1.06 Tue Aug 21 15:10:00 2012 - Fix a race condition stopping connections from closing properly. If you ask to open a channel, and then immediately try to close the connection then the not yet open channel would never remove itself from the associated connection, resulting in the connection - never being terminated (as there were still channels associated with - it). + never being terminated (as there were still channels associated + with it). - Stop leaking all RabbitMQ messages recieved back inside a closure. diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index ed7d78a..b00e421 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -188,10 +188,11 @@ sub _check_close_and_clean { my ($frame, $close_cb,) = @_; return 1 if !$frame->isa('Net::AMQP::Frame::Method'); + my $method_frame = $frame->method_frame; - return 1 if !$method_frame->isa('Net::AMQP::Protocol::Connection::Close') && !$method_frame->isa('Net::AMQP::Protocol::Connection::CloseOk'); - $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()) - if $method_frame->isa('Net::AMQP::Protocol::Connection::Close'); + return 1 if !$method_frame->isa('Net::AMQP::Protocol::Connection::Close'); + + $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()); $self->{_channels} = {}; $self->{_is_open} = 0; $self->_disconnect(); From 0cc859577a314e7d1f4eedf356d041b2bc6b0992 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 21 Aug 2012 15:08:42 +0200 Subject: [PATCH 006/165] Add repository metadata --- Makefile.PL | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile.PL b/Makefile.PL index 2ca2a19..f731120 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -18,5 +18,10 @@ install_share; build_requires 'Test::More'; build_requires 'Test::Exception'; build_requires 'version'; -auto_install; + +resources( + repository => 'git://github.com/bobtfish/AnyEvent-RabbitMQ.git', +); + WriteAll; + From aaf5720a89287f66c6648ba06f69869883b84898 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 21 Aug 2012 15:09:44 +0200 Subject: [PATCH 007/165] Add maintainer --- lib/AnyEvent/RabbitMQ.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index b00e421..60b55e1 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -585,6 +585,11 @@ AnyEvnet::RabbitMQ is known to work with RabbitMQ versions 2.5.1 and version 0-8 Masahito Ikuta Ecooldaemon@gmail.comE +=head1 MAINTAINER + +Currently maintained by C<< >> due to the original +author being missing in action. + =head1 COPYRIGHT Copyright (c) 2010, the above named author(s). @@ -597,3 +602,4 @@ This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut + From 343b6c4f8ccf5fce80a56ee7754d8c39b327acb6 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 21 Aug 2012 15:10:21 +0200 Subject: [PATCH 008/165] Version 1.06 --- lib/AnyEvent/RabbitMQ.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 60b55e1..4b6d131 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -20,7 +20,7 @@ use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.05'; +our $VERSION = '1.06'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-8.xml'; From 75c59c36774d2ed994f276d3cc0772a44264519e Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 21 Aug 2012 15:23:00 +0200 Subject: [PATCH 009/165] Update MANIFEST.SKIP --- MANIFEST.SKIP | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 3285585..3c25ee8 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -1,3 +1,4 @@ +^MYMETA\. \bRCS\b \bCVS\b ^MANIFEST\. From 1816ee8408b1bd4a6fd6dbc64e314e43aa0f06c8 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 21 Aug 2012 15:47:09 +0200 Subject: [PATCH 010/165] Fix indexing fail --- Changes | 3 +++ lib/AnyEvent/RabbitMQ/Channel.pm | 2 ++ lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 ++ 3 files changed, 7 insertions(+) diff --git a/Changes b/Changes index 4acad69..58c138c 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Fix dist by putting missing version numbers back into + all the modules. + 1.06 Tue Aug 21 15:10:00 2012 - Fix a race condition stopping connections from closing properly. If you ask to open a channel, and then immediately try to close diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 95419ab..90fda23 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -6,6 +6,8 @@ use warnings; use Scalar::Util qw(weaken); use AnyEvent::RabbitMQ::LocalQueue; +our $VERSION = '1.06'; + sub new { my $class = shift; my $self = bless { diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index dec6ba4..fdedbec 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,6 +3,8 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; +our $VERSION = '1.06'; + sub new { my $class = shift; return bless { From 13e5cc88ab615c5cba190a516a4553f31aaa3969 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 21 Aug 2012 15:48:05 +0200 Subject: [PATCH 011/165] Version 1.07 --- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 58c138c..7b83db1 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.07 Tue Aug 21 15:47:00 2012 - Fix dist by putting missing version numbers back into all the modules. diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 4b6d131..17faf35 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -20,7 +20,7 @@ use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.06'; +our $VERSION = '1.07'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-8.xml'; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 90fda23..ae25a39 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -6,7 +6,7 @@ use warnings; use Scalar::Util qw(weaken); use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.06'; +our $VERSION = '1.07'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index fdedbec..9a1463f 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.06'; +our $VERSION = '1.07'; sub new { my $class = shift; From b2d8a30b4f2ed0a0437816b71bc6a981e3d38924 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Sun, 26 Aug 2012 17:46:01 -0700 Subject: [PATCH 012/165] More thoroughly eliminate memory leaks on incoming messages --- lib/AnyEvent/RabbitMQ/Channel.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index ae25a39..0bfbbe6 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -654,7 +654,8 @@ sub _push_read_header_and_body { }); my $body_payload = ""; - my $next_frame; $next_frame = sub { + my $w_next_frame; + my $next_frame = sub { my $frame = shift; return $failure_cb->('Received data is not body frame') @@ -664,15 +665,16 @@ sub _push_read_header_and_body { if (length($body_payload) < $body_size) { # More to come - $self->{_content_queue}->get($next_frame); + $self->{_content_queue}->get($w_next_frame); } else { - undef $next_frame; $frame->payload($body_payload); $response->{body} = $frame; $cb->($response); } }; + $w_next_frame = $next_frame; + weaken($w_next_frame); $self->{_content_queue}->get($next_frame); From 662691404ddc125f531601b0ddb2c5320a85f7e2 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Sun, 26 Aug 2012 17:49:03 -0700 Subject: [PATCH 013/165] Properly handle channel close: Ensure pending requests fail immediately --- lib/AnyEvent/RabbitMQ/Channel.pm | 2 ++ lib/AnyEvent/RabbitMQ/LocalQueue.pm | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 0bfbbe6..bae38ff 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -598,6 +598,8 @@ sub push_queue_or_consume { ); $self->{_is_open} = 0; $self->{_is_active} = 0; + $self->{_queue}->_flush($frame); + $self->{_content_queue}->_flush($frame); $self->{connection}->delete_channel($self->{id}); $self->{on_close}->($frame); return $self; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 9a1463f..b2e838c 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -45,5 +45,15 @@ sub _drain_queue { return $self; } +sub _flush { + my ($self, $frame) = @_; + + $self->_drain_queue; + + while (my $cb = shift @{$self->{_drain_code_queue}}) { + eval { $cb->($frame) }; + } +} + 1; From 337cd29e82b00cd11d1040003be624a6ce77e6ca Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Sun, 26 Aug 2012 17:50:58 -0700 Subject: [PATCH 014/165] improve Data::Dumper options for protocol dumps --- lib/AnyEvent/RabbitMQ.pm | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 17faf35..bdd9f15 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -2,8 +2,6 @@ package AnyEvent::RabbitMQ; use strict; use warnings; - -use Data::Dumper; use Carp qw(confess croak); use List::MoreUtils qw(none); use Devel::GlobalDestruction; @@ -11,6 +9,17 @@ use namespace::clean; use File::ShareDir; use Readonly; +require Data::Dumper; +sub Dumper { + local $Data::Dumper::Terse = 1; + local $Data::Dumper::Indent = 1; + local $Data::Dumper::Useqq = 1; + local $Data::Dumper::Deparse = 1; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Sortkeys = 1; + &Data::Dumper::Dumper +} + use AnyEvent::Handle; use AnyEvent::Socket; From aff55a996a5ff9f550b07c29acee79b44a90fee9 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Sun, 26 Aug 2012 17:55:37 -0700 Subject: [PATCH 015/165] fix spelling errors --- lib/AnyEvent/RabbitMQ/Channel.pm | 12 ++++++------ xt/01_podspell.t | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index bae38ff..cfaed30 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -723,7 +723,7 @@ AnyEvent::RabbitMQ::Channel - Abstraction of an AMQP channel. my $ch = $rf->open_channel(); $ch->declare_exchange(exchange => 'test_exchange'); -=head1 DESRIPTION +=head1 DESCRIPTION =head1 METHODS @@ -842,7 +842,7 @@ supply a value if you want to be able to later cancel the subscription. =item on_success -Callback called if the subscription was successfull (before the first message is consumed). +Callback called if the subscription was successful (before the first message is consumed). =item on_failure @@ -855,7 +855,7 @@ Callback called if the subscription fails for any reason. Cancel a queue subscription. Note that the cancellation B take place at once, and further messages may be -consumed before the subscription is cancelled. No further messages will be +consumed before the subscription is canceled. No further messages will be consumed after the on_success callback has been called. Arguments: @@ -869,11 +869,11 @@ consumed from. =item on_success -Callback called if the subscription was successfully cancelled. +Callback called if the subscription was successfully canceled. =item on_failure -Callback called if the subscription could not be cancelled for any reason. +Callback called if the subscription could not be canceled for any reason. =back @@ -887,7 +887,7 @@ Arguments: =item queue -Mandatory. Name of the queue to try to recieve a message from. +Mandatory. Name of the queue to try to receive a message from. =item on_success diff --git a/xt/01_podspell.t b/xt/01_podspell.t index e09d03f..1dc139d 100644 --- a/xt/01_podspell.t +++ b/xt/01_podspell.t @@ -11,3 +11,5 @@ cooldaemon@gmail.com AMQP RabbitMQ multi +ack +qos From abf9c9746d4bf44a34062f452b2d314a290538fb Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 27 Aug 2012 08:46:24 +0100 Subject: [PATCH 016/165] Fix to UK spelling, as I am british ;P --- lib/AnyEvent/RabbitMQ/Channel.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index cfaed30..e1b2694 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -855,7 +855,7 @@ Callback called if the subscription fails for any reason. Cancel a queue subscription. Note that the cancellation B take place at once, and further messages may be -consumed before the subscription is canceled. No further messages will be +consumed before the subscription is cancelled. No further messages will be consumed after the on_success callback has been called. Arguments: @@ -869,11 +869,11 @@ consumed from. =item on_success -Callback called if the subscription was successfully canceled. +Callback called if the subscription was successfully cancelled. =item on_failure -Callback called if the subscription could not be canceled for any reason. +Callback called if the subscription could not be cancelled for any reason. =back @@ -896,7 +896,7 @@ a notification that there was nothing to collect from the queue. =item on_failure -This callback will be called if an error is signaled on this channel. +This callback will be called if an error is signalled on this channel. =back From 055ce24222a222caca6714091338dac973a387a4 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 27 Aug 2012 08:42:48 +0100 Subject: [PATCH 017/165] Changelog --- Changes | 6 ++++++ lib/AnyEvent/RabbitMQ/LocalQueue.pm | 1 + 2 files changed, 7 insertions(+) diff --git a/Changes b/Changes index 7b83db1..c62e693 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Improve Data::Dumper options for protocol dumps (Chip Salzenberg) + - More thoroughly eliminate memory leaks on incoming messages + (Chip Salzenberg) + - Properly handle channel close: Ensure pending requests fail + immediately (Chip Salzenberg) + 1.07 Tue Aug 21 15:47:00 2012 - Fix dist by putting missing version numbers back into all the modules. diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index b2e838c..4a0e655 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -51,6 +51,7 @@ sub _flush { $self->_drain_queue; while (my $cb = shift @{$self->{_drain_code_queue}}) { + local $@; # Flush frames immediately, throwing away errors for on-close eval { $cb->($frame) }; } } From 493dd04905c37031ace9d2c01a12da78756b2c1b Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 27 Aug 2012 08:43:24 +0100 Subject: [PATCH 018/165] Version 1.08 --- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index c62e693..e361cf1 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.08 Mon Aug 27 08:43:00 - Improve Data::Dumper options for protocol dumps (Chip Salzenberg) - More thoroughly eliminate memory leaks on incoming messages (Chip Salzenberg) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index bdd9f15..48e89e0 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -29,7 +29,7 @@ use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.07'; +our $VERSION = '1.08'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-8.xml'; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index e1b2694..41d1f92 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -6,7 +6,7 @@ use warnings; use Scalar::Util qw(weaken); use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.07'; +our $VERSION = '1.08'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 4a0e655..e804d65 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.07'; +our $VERSION = '1.08'; sub new { my $class = shift; From 0d79a27881c263cd8641a768b8aa9bb4702b5ece Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Sat, 8 Sep 2012 20:58:15 +0100 Subject: [PATCH 019/165] Partial fix for RT#79511 --- Changes | 8 ++++++++ lib/AnyEvent/RabbitMQ/Channel.pm | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index e361cf1..36ea47b 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,13 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Stop defining a _return_cb value when not using the mandatory + or immediate flags when publishing a message. This means that + if you're not using these flags, but are using an infinite set + of routing keys, then you won't leak infinite RAM. + Currently if you do use these flags and infinitely variable + routing keys, we still have a problem as we leak callbacks. + RT#79511 + 1.08 Mon Aug 27 08:43:00 - Improve Data::Dumper options for protocol dumps (Chip Salzenberg) - More thoroughly eliminate memory leaks on incoming messages diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 41d1f92..5cd2bb1 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -297,9 +297,11 @@ sub publish { return $self if !$args{mandatory} && !$args{immediate}; - $self->{_return_cbs}->{ - ($args{exchange} || '') . '_' . $args{routing_key} - } = $return_cb; + if ($args{mandatory} || $args{immediate}) { + $self->{_return_cbs}->{ + ($args{exchange} || '') . '_' . $args{routing_key} + } = $return_cb; + } return $self; } From 9bc9819ce794c969c5441416ff6617987a8e1148 Mon Sep 17 00:00:00 2001 From: Viktor Voronin Date: Wed, 28 Nov 2012 00:58:33 +0400 Subject: [PATCH 020/165] heartbeat handle & response --- lib/AnyEvent/RabbitMQ.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 48e89e0..48b8dd5 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -173,6 +173,10 @@ sub _read_loop { my $id = $frame->channel; if (0 == $id) { + if ($frame->type_id == 8) { + $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); + return; + } return if !$self->_check_close_and_clean($frame, $close_cb,); $self->{_queue}->push($frame); } else { From 5244d524cc365979c4b0988a805c9be81ae370d4 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 18 Oct 2012 18:21:06 +0100 Subject: [PATCH 021/165] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5a5ef1b..2d74792 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +AnyEvent-RabbitMQ-* +*.swp MYMETA.* cover_db META.yml From 7e7ee5e594e69225b41b6563295b18efaffecf3c Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Wed, 12 Sep 2012 15:14:44 -0700 Subject: [PATCH 022/165] AMQP 0.9 support, ->confirm, more care that all on_close callbacks will be called correctly, and potential memory leak fixes --- lib/AnyEvent/RabbitMQ.pm | 59 ++++---- lib/AnyEvent/RabbitMQ/Channel.pm | 229 +++++++++++++++++++++++-------- xt/04_anyevent.t | 10 +- xt/05_multi_channel.t | 3 +- 4 files changed, 219 insertions(+), 82 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 48b8dd5..99b7065 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -5,9 +5,9 @@ use warnings; use Carp qw(confess croak); use List::MoreUtils qw(none); use Devel::GlobalDestruction; -use namespace::clean; use File::ShareDir; use Readonly; +use namespace::clean; require Data::Dumper; sub Dumper { @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; our $VERSION = '1.08'; Readonly my $DEFAULT_AMQP_SPEC - => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-8.xml'; + => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; sub new { my $class = shift; @@ -296,7 +296,6 @@ sub _open { 'Connection::Open', { virtual_host => $args{vhost}, - capabilities => '', insist => 1, }, 'Connection::OpenOk', @@ -320,36 +319,40 @@ sub close { return $self; } - my $close_cb = sub { - $self->_close( - sub { - $self->_disconnect(); - $args{on_success}->(@_); - }, - sub { - $self->_disconnect(); - $args{on_failure}->(@_); - } - ); - return $self; - }; - - if (0 == scalar keys %{$self->{_channels}}) { - return $close_cb->(); - } + my $channels_cv = AnyEvent->condvar; + $channels_cv->begin( + sub { + $self->_close( + sub { + $self->_disconnect(); + $args{on_success}->(@_); + }, + sub { + $self->_disconnect(); + $args{on_failure}->(@_); + } + ); + } + ); for my $id (keys %{$self->{_channels}}) { my $channel = $self->{_channels}->{$id} or next; # Could have already gone away on global destruction.. + + $channels_cv->begin; $channel->close( - on_success => $close_cb, + on_success => sub { + $channels_cv->end; + }, on_failure => sub { - $close_cb->(); + $channels_cv->end; $args{on_failure}->(@_); }, ); } + $channels_cv->end; + return $self; } @@ -357,7 +360,15 @@ sub _close { my $self = shift; my ($cb, $failure_cb,) = @_; - return $self if !$self->{_is_open} || 0 < scalar keys %{$self->{_channels}}; + if (!$self->{_is_open}) { + $cb->("Already closed"); + return $self; + } + + if (my @ch = keys %{$self->{_channels}}) { + $failure_cb->("Can't disconnect with channel(s) open: @ch"); + return $self; + } $self->_push_write_and_read( 'Connection::Close', {}, 'Connection::CloseOk', @@ -592,7 +603,7 @@ You can use AnyEvent::RabbitMQ to - * Publish, consume, get, ack, recover and reject messages * Select, commit and rollback transactions -AnyEvnet::RabbitMQ is known to work with RabbitMQ versions 2.5.1 and version 0-8 of the AMQP specification. +AnyEvent::RabbitMQ is known to work with RabbitMQ versions 2.5.1 and versions 0-8 and 0-9-1 of the AMQP specification. =head1 AUTHOR diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 5cd2bb1..f3390e2 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -5,21 +5,35 @@ use warnings; use Scalar::Util qw(weaken); use AnyEvent::RabbitMQ::LocalQueue; +BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } our $VERSION = '1.08'; sub new { my $class = shift; + my $self = bless { - @_, # id, connection, on_close - _is_open => 0, - _is_active => 0, + @_, # id, connection, on_return, on_close _queue => AnyEvent::RabbitMQ::LocalQueue->new, _content_queue => AnyEvent::RabbitMQ::LocalQueue->new, - _consumer_cbs => {}, - _return_cbs => {}, }, $class; weaken($self->{connection}); + return $self->_reset; +} + +sub _reset { + my $self = shift; + + my %a = ( + _is_open => 0, + _is_active => 0, + _is_confirm => 0, + _publish_tag => 0, + _publish_cbs => {}, + _consumer_cbs => {}, + ); + @$self{keys %a} = values %a; + return $self; } @@ -65,45 +79,33 @@ sub close { # open, but we've closed it - a more elegant fix would be to mark that # the channel is opening, and wait for it to open before closing it if (!$self->{_is_open}) { - $self->{connection}->delete_channel($self->{id}); + $connection->delete_channel($self->{id}); $args{on_success}->($self); return $self; } - return $self->_close(%args) if 0 == scalar keys %{$self->{_consumer_cbs}}; - - for my $consumer_tag (keys %{$self->{_consumer_cbs}}) { - $self->cancel( - consumer_tag => $consumer_tag, - on_success => sub { - $self->_close(%args); - }, - on_failure => sub { - $self->_close(%args); - $args{on_failure}->(@_); - } - ); - } + # spell it out, so the callbacks always can call ->method_frame + my $close_frame = Net::AMQP::Frame::Method->new( + method_frame => Net::AMQP::Protocol::Channel::Close->new, + ); - return $self; -} + $connection->_push_write( + $close_frame, + $self->{id}, + ); -sub _close { - my $self = shift; - my %args = @_; + weaken(my $wself = $self); - $self->{connection}->_push_write_and_read( - 'Channel::Close', {}, 'Channel::CloseOk', + $connection->_push_read_and_valid( + 'Channel::CloseOk', sub { - $self->{_is_open} = 0; - $self->{_is_active} = 0; - $self->{connection}->delete_channel($self->{id}); + my $me = $wself or return; + $me->_close($close_frame, 0); $args{on_success}->(); }, sub { - $self->{_is_open} = 0; - $self->{_is_active} = 0; - $self->{connection}->delete_channel($self->{id}); + my $me = $wself or return; + $me->_close($close_frame, 0); $args{on_failure}->(); }, $self->{id}, @@ -112,6 +114,29 @@ sub _close { return $self; } +sub _close { + my $self = shift; + my ($frame, $forced) = @_; + + my $connection = $self->{connection}; + my $on_close = $self->{on_close}; + + $self->{_is_open} = 0; + $self->{_queue}->_flush($frame); + $self->{_content_queue}->_flush($frame); + $self->_reset; + + $connection->delete_channel($self->{id}) if $connection; + + if (defined $on_close) { + local $@; + $on_close->($frame); + warn "Error in callback, ignored:\n $@ " if $@; + } + + return $self; +} + sub declare_exchange { my $self = shift; my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); @@ -283,9 +308,13 @@ sub publish { return $self if !$self->{_is_active}; - my $header_args = delete $args{header} || {}; - my $body = delete $args{body} || ''; - my $return_cb = delete $args{on_return} || sub {}; + my $header_args = delete $args{header}; + my $body = delete $args{body}; + my $ack_cb = delete $args{on_ack}; + + defined($header_args) or $header_args = {}; + defined($body) or $body = ''; + defined($ack_cb) or $ack_cb = sub {}; $self->_publish( %args, @@ -295,12 +324,14 @@ sub publish { $body, ); - return $self if !$args{mandatory} && !$args{immediate}; - - if ($args{mandatory} || $args{immediate}) { - $self->{_return_cbs}->{ - ($args{exchange} || '') . '_' . $args{routing_key} - } = $return_cb; + if ($self->{_is_confirm}) { + # yeah, they're sequential. see Java client + my $tag = ++$self->{_publish_tag}; + $self->{_publish_cbs}{$tag} = $ack_cb; + } + else { + # we do not expect an ack, so assume success + $ack_cb->(); } return $self; @@ -504,9 +535,37 @@ sub qos { return $self; } +sub confirm { + my $self = shift; + my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); + + return $self if !$self->_check_open($failure_cb); + return $self if !$self->_check_version(0, 9, $failure_cb); + + weaken(my $wself = $self); + + $self->{connection}->_push_write_and_read( + 'Confirm::Select', + { + %args, + nowait => 0, # FIXME + }, + 'Confirm::SelectOk', + sub { + my $me = $wself or return; + $me->{_is_confirm} = 1; + $cb->(); + }, + $failure_cb, + $self->{id}, + ); + + return $self; +} + sub recover { my $self = shift; - my %args = @_; + my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); return $self if !$self->_check_open(sub {}); @@ -518,6 +577,18 @@ sub recover { $self->{id}, ); + if (!$args{nowait} && $self->_check_version(0, 9)) { + $self->{connection}->_push_read_and_valid( + 'Basic::RecoverOk', + $cb, + $failure_cb, + $self->{id}, + ); + } + else { + $cb->(); + } + return $self; } @@ -598,12 +669,7 @@ sub push_queue_or_consume { Net::AMQP::Protocol::Channel::CloseOk->new(), $self->{id}, ); - $self->{_is_open} = 0; - $self->{_is_active} = 0; - $self->{_queue}->_flush($frame); - $self->{_content_queue}->_flush($frame); - $self->{connection}->delete_channel($self->{id}); - $self->{on_close}->($frame); + $self->_close($frame, 0); return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Deliver')) { my $cb = $self->{_consumer_cbs}->{ @@ -612,11 +678,20 @@ sub push_queue_or_consume { $self->_push_read_header_and_body('deliver', $frame, $cb, $failure_cb); return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Return')) { - my $cb = $self->{_return_cbs}->{ - $method_frame->exchange . '_' . $method_frame->routing_key - } || sub {}; + my $cb = $self->{on_return} || $failure_cb; $self->_push_read_header_and_body('return', $frame, $cb, $failure_cb); return $self; + } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Ack')) { + my $pub_cb; + if (!$self->{_is_confirm}) { + $failure_cb->("Received Ack when not in confirm mode"); + } + elsif (not $pub_cb = delete $self->{_publish_cbs}{$method_frame->{delivery_tag}}) { + $failure_cb->("Received Ack of unknown delivery tag $method_frame->{delivery_tag}"); + } + else { + $pub_cb->(); + } } elsif ($method_frame->isa('Net::AMQP::Protocol::Channel::Flow')) { $self->{_is_active} = $method_frame->active; $self->{connection}->_push_write( @@ -657,11 +732,14 @@ sub _push_read_header_and_body { $body_size = $frame->body_size; }); + weaken(my $wcontq = $self->{_content_queue}); my $body_payload = ""; my $w_next_frame; my $next_frame = sub { my $frame = shift; + my $contq = $wcontq or return; + return $failure_cb->('Received data is not body frame') if !$frame->isa('Net::AMQP::Frame::Body'); @@ -669,7 +747,7 @@ sub _push_read_header_and_body { if (length($body_payload) < $body_size) { # More to come - $self->{_content_queue}->get($w_next_frame); + $contq->get($w_next_frame); } else { $frame->payload($body_payload); @@ -705,9 +783,22 @@ sub _check_open { return 0; } +sub _check_version { + my $self = shift; + my ($major, $minor, $failure_cb) = @_; + + my $amaj = $Net::AMQP::Protocol::VERSION_MAJOR; + my $amin = $Net::AMQP::Protocol::VERSION_MINOR; + + return 1 if $amaj >= $major || $amaj == $major && $amin >= $minor; + + $failure_cb->("Not supported in AMQP $amaj-$amin") if $failure_cb; + return 0; +} + sub DESTROY { my $self = shift; - $self->close() if defined $self; + $self->close() if $self->{_is_open}; return; } @@ -727,6 +818,23 @@ AnyEvent::RabbitMQ::Channel - Abstraction of an AMQP channel. =head1 DESCRIPTION +=head1 ARGUMENTS FOR C + +=over + +=item on_close + +Callback invoked when the channel closes. Callback will be passed the +incoming message that caused the close, if any. + +=item on_return + +Callback invoked when a mandatory or immediate message publish fails. +Callback will be passed the incoming message, with accessors +C, C, and C. + +=back + =head1 METHODS =head2 declare_exchange (%args) @@ -815,6 +923,10 @@ Arguments: The text body of the message to send. +=item header + +Customer headers for the message (if any). + =item exchange The name of the exchange to send the message to. @@ -823,6 +935,10 @@ The name of the exchange to send the message to. The routing key with which to publish the message. +=item on_ack + +Callback (if any) for confirming acknowledgment when in confirm mode. + =back =head2 consume @@ -906,6 +1022,11 @@ This callback will be called if an error is signalled on this channel. =head2 qos +=head2 confirm + +Put channel into confirm mode. In confirm mode, publishes are confirmed by +the server, so the on_ack callback of publish works. + =head2 recover =head2 select_tx diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index ca3de56..205129a 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -1,5 +1,6 @@ use Test::More; use Test::Exception; +use Data::Dumper; use FindBin; use version; @@ -15,6 +16,7 @@ my %conf = ( user => 'guest', pass => 'guest', vhost => '/', + verbose => 1, ); eval { @@ -36,7 +38,7 @@ plan tests => 31; use AnyEvent::RabbitMQ; -my $ar = AnyEvent::RabbitMQ->new(); +my $ar = AnyEvent::RabbitMQ->new(verbose => $conf{verbose}); lives_ok sub { $ar->load_xml_spec() @@ -56,7 +58,8 @@ $ar->connect( on_failure => failure_cb($done), on_close => sub { my $method_frame = shift->method_frame; - die $method_frame->reply_code, $method_frame->reply_text; + die $method_frame->reply_code, $method_frame->reply_text + if $method_frame->reply_code; }, ); $done->recv; @@ -72,7 +75,8 @@ $ar->open_channel( on_failure => failure_cb($done), on_close => sub { my $method_frame = shift->method_frame; - die $method_frame->reply_code, $method_frame->reply_text; + die $method_frame->reply_code, $method_frame->reply_text + if $method_frame->reply_code; }, ); $done->recv; diff --git a/xt/05_multi_channel.t b/xt/05_multi_channel.t index bdcaaa6..774129b 100644 --- a/xt/05_multi_channel.t +++ b/xt/05_multi_channel.t @@ -163,6 +163,7 @@ sub publish { sub handle_close { my $method_frame = shift->method_frame; - die $method_frame->reply_code, $method_frame->reply_text; + die $method_frame->reply_code, $method_frame->reply_text + if $method_frame->reply_code; } From b462a26d5eec2eda07daebfec057960e8ca07af7 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Wed, 12 Sep 2012 15:17:24 -0700 Subject: [PATCH 023/165] include amqp 0.9.1 spec, duh --- share/fixed_amqp0-9-1.xml | 3320 +++++++++++++++++++++++++++++++++++++ 1 file changed, 3320 insertions(+) create mode 100644 share/fixed_amqp0-9-1.xml diff --git a/share/fixed_amqp0-9-1.xml b/share/fixed_amqp0-9-1.xml new file mode 100644 index 0000000..7b42816 --- /dev/null +++ b/share/fixed_amqp0-9-1.xml @@ -0,0 +1,3320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates that the method completed successfully. This reply code is + reserved for future use - the current protocol design does not use positive + confirmation and reply codes are sent only in case of an error. + + + + + + The client attempted to transfer content larger than the server could accept + at the present time. The client may retry at a later time. + + + + + + When the exchange cannot deliver to a consumer when the immediate flag is + set. As a result of pending data on the queue or the absence of any + consumers of the queue. + + + + + + An operator intervened to close the connection for some reason. The client + may retry at some later date. + + + + + + The client tried to work with an unknown virtual host. + + + + + + The client attempted to work with a server entity to which it has no + access due to security settings. + + + + + + The client attempted to work with a server entity that does not exist. + + + + + + The client attempted to work with a server entity to which it has no + access because another client is working with it. + + + + + + The client requested a method that was not allowed because some precondition + failed. + + + + + + The sender sent a malformed frame that the recipient could not decode. + This strongly implies a programming error in the sending peer. + + + + + + The sender sent a frame that contained illegal values for one or more + fields. This strongly implies a programming error in the sending peer. + + + + + + The client sent an invalid sequence of frames, attempting to perform an + operation that was considered invalid by the server. This usually implies + a programming error in the client. + + + + + + The client attempted to work with a channel that had not been correctly + opened. This most likely indicates a fault in the client layer. + + + + + + The peer sent a frame that was not expected, usually in the context of + a content header and body. This strongly indicates a fault in the peer's + content processing. + + + + + + The server could not complete the method because it lacked sufficient + resources. This may be due to the client creating too many of some type + of entity. + + + + + + The client tried to work with some entity in a manner that is prohibited + by the server, due to security settings or by some other criteria. + + + + + + The client tried to use functionality that is not implemented in the + server. + + + + + + The server could not complete the method because of an internal error. + The server may require intervention by an operator in order to resume + normal operations. + + + + + + + + + + Identifier for the consumer, valid within the current channel. + + + + + + The server-assigned and channel-specific delivery tag + + + + The delivery tag is valid only within the channel from which the message was + received. I.e. a client MUST NOT receive a message on one channel and then + acknowledge it on another. + + + + + The server MUST NOT use a zero value for delivery tags. Zero is reserved + for client use, meaning "all messages so far received". + + + + + + + The exchange name is a client-selected string that identifies the exchange for + publish methods. + + + + + + + + + + If this field is set the server does not expect acknowledgements for + messages. That is, when a message is delivered to the client the server + assumes the delivery will succeed and immediately dequeues it. This + functionality may increase performance but at the cost of reliability. + Messages can get lost if a client dies before they are delivered to the + application. + + + + + + If the no-local field is set the server will not send messages to the connection that + published them. + + + + + + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + + + + + + Unconstrained. + + + + + + + + This table provides a set of peer properties, used for identification, debugging, + and general information. + + + + + + The queue name identifies the queue within the vhost. In methods where the queue + name may be blank, and that has no specific significance, this refers to the + 'current' queue for the channel, meaning the last queue that the client declared + on the channel. If the client did not declare a queue, and the method needs a + queue name, this will result in a 502 (syntax error) channel exception. + + + + + + + + This indicates that the message has been previously delivered to this or + another client. + + + + The server SHOULD try to signal redelivered messages when it can. When + redelivering a message that was not successfully acknowledged, the server + SHOULD deliver it to the original client if possible. + + + Declare a shared queue and publish a message to the queue. Consume the + message using explicit acknowledgements, but do not acknowledge the + message. Close the connection, reconnect, and consume from the queue + again. The message should arrive with the redelivered flag set. + + + + + The client MUST NOT rely on the redelivered field but should take it as a + hint that the message may already have been processed. A fully robust + client must be able to track duplicate received messages on non-transacted, + and locally-transacted channels. + + + + + + + The number of messages in the queue, which will be zero for newly-declared + queues. This is the number of messages present in the queue, and committed + if the channel on which they were published is transacted, that are not + waiting acknowledgement. + + + + + + The reply code. The AMQ reply codes are defined as constants at the start + of this formal specification. + + + + + + + The localised reply text. This text can be logged as an aid to resolving + issues. + + + + + + + + + + + + + + + + + + + + The connection class provides methods for a client to establish a network connection to + a server, and for both peers to operate the connection thereafter. + + + + connection = open-connection *use-connection close-connection + open-connection = C:protocol-header + S:START C:START-OK + *challenge + S:TUNE C:TUNE-OK + C:OPEN S:OPEN-OK + challenge = S:SECURE C:SECURE-OK + use-connection = *channel + close-connection = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK + + + + + + + + + + This method starts the connection negotiation process by telling the client the + protocol version that the server proposes, along with a list of security mechanisms + which the client can use for authentication. + + + + + If the server cannot support the protocol specified in the protocol header, + it MUST respond with a valid protocol header and then close the socket + connection. + + + The client sends a protocol header containing an invalid protocol name. + The server MUST respond by sending a valid protocol header and then closing + the connection. + + + + + The server MUST provide a protocol version that is lower than or equal to + that requested by the client in the protocol header. + + + The client requests a protocol version that is higher than any valid + implementation, e.g. 2.0. The server must respond with a protocol header + indicating its supported protocol version, e.g. 1.0. + + + + + If the client cannot handle the protocol version suggested by the server + it MUST close the socket connection without sending any further data. + + + The server sends a protocol version that is lower than any valid + implementation, e.g. 0.1. The client must respond by closing the + connection without sending any further data. + + + + + + + + + The major version number can take any value from 0 to 99 as defined in the + AMQP specification. + + + + + + The minor version number can take any value from 0 to 99 as defined in the + AMQP specification. + + + + + + + The properties SHOULD contain at least these fields: "host", specifying the + server host name or address, "product", giving the name of the server product, + "version", giving the name of the server version, "platform", giving the name + of the operating system, "copyright", if appropriate, and "information", giving + other general information. + + + Client connects to server and inspects the server properties. It checks for + the presence of the required fields. + + + + + + + A list of the security mechanisms that the server supports, delimited by spaces. + + + + + + + A list of the message locales that the server supports, delimited by spaces. The + locale defines the language in which the server will send reply texts. + + + + The server MUST support at least the en_US locale. + + + Client connects to server and inspects the locales field. It checks for + the presence of the required locale(s). + + + + + + + + + This method selects a SASL security mechanism. + + + + + + + + + The properties SHOULD contain at least these fields: "product", giving the name + of the client product, "version", giving the name of the client version, "platform", + giving the name of the operating system, "copyright", if appropriate, and + "information", giving other general information. + + + + + + + A single security mechanisms selected by the client, which must be one of those + specified by the server. + + + + The client SHOULD authenticate using the highest-level security profile it + can handle from the list provided by the server. + + + + + If the mechanism field does not contain one of the security mechanisms + proposed by the server in the Start method, the server MUST close the + connection without sending any further data. + + + Client connects to server and sends an invalid security mechanism. The + server must respond by closing the connection (a socket close, with no + connection close negotiation). + + + + + + + + A block of opaque data passed to the security mechanism. The contents of this + data are defined by the SASL security mechanism. + + + + + + + A single message locale selected by the client, which must be one of those + specified by the server. + + + + + + + + + + The SASL protocol works by exchanging challenges and responses until both peers have + received sufficient information to authenticate each other. This method challenges + the client to provide more information. + + + + + + + + Challenge information, a block of opaque binary data passed to the security + mechanism. + + + + + + + This method attempts to authenticate, passing a block of SASL data for the security + mechanism at the server side. + + + + + + + A block of opaque data passed to the security mechanism. The contents of this + data are defined by the SASL security mechanism. + + + + + + + + + + This method proposes a set of connection configuration values to the client. The + client can accept and/or adjust these. + + + + + + + + + Specifies highest channel number that the server permits. Usable channel numbers + are in the range 1..channel-max. Zero indicates no specified limit. + + + + + + The largest frame size that the server proposes for the connection, including + frame header and end-byte. The client can negotiate a lower value. Zero means + that the server does not impose any specific limit but may reject very large + frames if it cannot allocate resources for them. + + + + Until the frame-max has been negotiated, both peers MUST accept frames of up + to frame-min-size octets large, and the minimum negotiated value for frame-max + is also frame-min-size. + + + Client connects to server and sends a large properties field, creating a frame + of frame-min-size octets. The server must accept this frame. + + + + + + + The delay, in seconds, of the connection heartbeat that the server wants. + Zero means the server does not want a heartbeat. + + + + + + + This method sends the client's connection tuning parameters to the server. + Certain fields are negotiated, others provide capability information. + + + + + + + The maximum total number of channels that the client will use per connection. + + + + If the client specifies a channel max that is higher than the value provided + by the server, the server MUST close the connection without attempting a + negotiated close. The server may report the error in some fashion to assist + implementors. + + + + + + + + + The largest frame size that the client and server will use for the connection. + Zero means that the client does not impose any specific limit but may reject + very large frames if it cannot allocate resources for them. Note that the + frame-max limit applies principally to content frames, where large contents can + be broken into frames of arbitrary size. + + + + Until the frame-max has been negotiated, both peers MUST accept frames of up + to frame-min-size octets large, and the minimum negotiated value for frame-max + is also frame-min-size. + + + + + If the client specifies a frame max that is higher than the value provided + by the server, the server MUST close the connection without attempting a + negotiated close. The server may report the error in some fashion to assist + implementors. + + + + + + + The delay, in seconds, of the connection heartbeat that the client wants. Zero + means the client does not want a heartbeat. + + + + + + + + + This method opens a connection to a virtual host, which is a collection of + resources, and acts to separate multiple application domains within a server. + The server may apply arbitrary limits per virtual host, such as the number + of each type of entity that may be used, per connection and/or in total. + + + + + + + + The name of the virtual host to work with. + + + + If the server supports multiple virtual hosts, it MUST enforce a full + separation of exchanges, queues, and all associated entities per virtual + host. An application, connected to a specific virtual host, MUST NOT be able + to access resources of another virtual host. + + + + + The server SHOULD verify that the client has permission to access the + specified virtual host. + + + + + + + + + + + + This method signals to the client that the connection is ready for use. + + + + + + + + + + + This method indicates that the sender wants to close the connection. This may be + due to internal conditions (e.g. a forced shut-down) or due to an error handling + a specific method, i.e. an exception. When a close is due to an exception, the + sender provides the class and method id of the method which caused the exception. + + + + After sending this method, any received methods except Close and Close-OK MUST + be discarded. The response to receiving a Close after sending Close must be to + send Close-Ok. + + + + + + + + + + + + + When the close is provoked by a method exception, this is the class of the + method. + + + + + + When the close is provoked by a method exception, this is the ID of the method. + + + + + + + This method confirms a Connection.Close method and tells the recipient that it is + safe to release resources for the connection and close the socket. + + + + A peer that detects a socket closure without having received a Close-Ok + handshake method SHOULD log the error. + + + + + + + + + + + + The channel class provides methods for a client to establish a channel to a + server and for both peers to operate the channel thereafter. + + + + channel = open-channel *use-channel close-channel + open-channel = C:OPEN S:OPEN-OK + use-channel = C:FLOW S:FLOW-OK + / S:FLOW C:FLOW-OK + / functional-class + close-channel = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK + + + + + + + + + + This method opens a channel to the server. + + + + The client MUST NOT use this method on an already-opened channel. + + + Client opens a channel and then reopens the same channel. + + + + + + + + + + + This method signals to the client that the channel is ready for use. + + + + + + + + + + + This method asks the peer to pause or restart the flow of content data sent by + a consumer. This is a simple flow-control mechanism that a peer can use to avoid + overflowing its queues or otherwise finding itself receiving more messages than + it can process. Note that this method is not intended for window control. It does + not affect contents returned by Basic.Get-Ok methods. + + + + + When a new channel is opened, it is active (flow is active). Some applications + assume that channels are inactive until started. To emulate this behaviour a + client MAY open the channel, then pause it. + + + + + + When sending content frames, a peer SHOULD monitor the channel for incoming + methods and respond to a Channel.Flow as rapidly as possible. + + + + + + A peer MAY use the Channel.Flow method to throttle incoming content data for + internal reasons, for example, when exchanging data over a slower connection. + + + + + + The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer + that does not respect the request. This is to prevent badly-behaved clients + from overwhelming a server. + + + + + + + + + + + If 1, the peer starts sending content frames. If 0, the peer stops sending + content frames. + + + + + + + Confirms to the peer that a flow command was received and processed. + + + + + + Confirms the setting of the processed flow method: 1 means the peer will start + sending or continue to send content frames; 0 means it will not. + + + + + + + + + This method indicates that the sender wants to close the channel. This may be due to + internal conditions (e.g. a forced shut-down) or due to an error handling a specific + method, i.e. an exception. When a close is due to an exception, the sender provides + the class and method id of the method which caused the exception. + + + + After sending this method, any received methods except Close and Close-OK MUST + be discarded. The response to receiving a Close after sending Close must be to + send Close-Ok. + + + + + + + + + + + + + When the close is provoked by a method exception, this is the class of the + method. + + + + + + When the close is provoked by a method exception, this is the ID of the method. + + + + + + + This method confirms a Channel.Close method and tells the recipient that it is safe + to release resources for the channel. + + + + A peer that detects a socket closure without having received a Channel.Close-Ok + handshake method SHOULD log the error. + + + + + + + + + + + + Exchanges match and distribute messages across queues. Exchanges can be configured in + the server or declared at runtime. + + + + exchange = C:DECLARE S:DECLARE-OK + / C:DELETE S:DELETE-OK + / C:BIND S:BIND-OK + / C:UNBIND S:UNBIND-OK + + + + + + + + The server MUST implement these standard exchange types: fanout, direct. + + + Client attempts to declare an exchange with each of these standard types. + + + + + The server SHOULD implement these standard exchange types: topic, headers. + + + Client attempts to declare an exchange with each of these standard types. + + + + + The server MUST, in each virtual host, pre-declare an exchange instance + for each standard exchange type that it implements, where the name of the + exchange instance, if defined, is "amq." followed by the exchange type name. + + + The server MUST, in each virtual host, pre-declare at least two direct + exchange instances: one named "amq.direct", the other with no public name + that serves as a default exchange for Publish methods. + + + Client declares a temporary queue and attempts to bind to each required + exchange instance ("amq.fanout", "amq.direct", "amq.topic", and "amq.headers" + if those types are defined). + + + + + The server MUST pre-declare a direct exchange with no public name to act as + the default exchange for content Publish methods and for default queue bindings. + + + Client checks that the default exchange is active by specifying a queue + binding with no exchange name, and publishing a message with a suitable + routing key but without specifying the exchange name, then ensuring that + the message arrives in the queue correctly. + + + + + The server MUST NOT allow clients to access the default exchange except + by specifying an empty exchange name in the Queue.Bind and content Publish + methods. + + + + + The server MAY implement other exchange types as wanted. + + + + + + + + This method creates an exchange if it does not already exist, and if the exchange + exists, verifies that it is of the correct and expected class. + + + + The server SHOULD support a minimum of 16 exchanges per virtual host and + ideally, impose no limit except as defined by available resources. + + + The client declares as many exchanges as it can until the server reports + an error; the number of exchanges successfully declared must be at least + sixteen. + + + + + + + + + + + + + Exchange names starting with "amq." are reserved for pre-declared and + standardised exchanges. The client MAY declare an exchange starting with + "amq." if the passive option is set, or the exchange already exists. + + + The client attempts to declare a non-existing exchange starting with + "amq." and with the passive option set to zero. + + + + + The exchange name consists of a non-empty sequence of these characters: + letters, digits, hyphen, underscore, period, or colon. + + + The client attempts to declare an exchange with an illegal name. + + + + + + + + Each exchange belongs to one of a set of exchange types implemented by the + server. The exchange types define the functionality of the exchange - i.e. how + messages are routed through it. It is not valid or meaningful to attempt to + change the type of an existing exchange. + + + + Exchanges cannot be redeclared with different types. The client MUST not + attempt to redeclare an existing exchange with a different type than used + in the original Exchange.Declare method. + + + TODO. + + + + + The client MUST NOT attempt to declare an exchange with a type that the + server does not support. + + + TODO. + + + + + + + If set, the server will reply with Declare-Ok if the exchange already + exists with the same name, and raise an error if not. The client can + use this to check whether an exchange exists without modifying the + server state. When set, all other method fields except name and no-wait + are ignored. A declare with both passive and no-wait has no effect. + Arguments are compared for semantic equivalence. + + + + If set, and the exchange does not already exist, the server MUST + raise a channel exception with reply code 404 (not found). + + + TODO. + + + + + If not set and the exchange exists, the server MUST check that the + existing exchange has the same values for type, durable, and arguments + fields. The server MUST respond with Declare-Ok if the requested + exchange matches these fields, and MUST raise a channel exception if + not. + + + TODO. + + + + + + + If set when creating a new exchange, the exchange will be marked as durable. + Durable exchanges remain active when a server restarts. Non-durable exchanges + (transient exchanges) are purged if/when a server restarts. + + + + The server MUST support both durable and transient exchanges. + + + TODO. + + + + + + + If set, the exchange is deleted when all queues have + finished using it. + + + + The server SHOULD allow for a reasonable delay between the + point when it determines that an exchange is not being + used (or no longer used), and the point when it deletes + the exchange. At the least it must allow a client to + create an exchange and then bind a queue to it, with a + small but non-zero delay between these two actions. + + + + + The server MUST ignore the auto-delete field if the + exchange already exists. + + + + + + + If set, the exchange may not be used directly by publishers, + but only when bound to other exchanges. Internal exchanges + are used to construct wiring that is not visible to + applications. + + + + + + + + A set of arguments for the declaration. The syntax and semantics of these + arguments depends on the server implementation. + + + + + + + This method confirms a Declare method and confirms the name of the exchange, + essential for automatically-named exchanges. + + + + + + + + + This method deletes an exchange. When an exchange is deleted all queue bindings on + the exchange are cancelled. + + + + + + + + + + + + The client MUST NOT attempt to delete an exchange that does not exist. + + + + + + + + If set, the server will only delete the exchange if it has no queue bindings. If + the exchange has queue bindings the server does not delete it but raises a + channel exception instead. + + + + The server MUST NOT delete an exchange that has bindings on it, if the if-unused + field is true. + + + The client declares an exchange, binds a queue to it, then tries to delete it + setting if-unused to true. + + + + + + + + + This method confirms the deletion of an exchange. + + + + + + + + This method binds an exchange to an exchange. + + + + A server MUST allow and ignore duplicate bindings - that is, + two or more bind methods for a specific exchanges, with + identical arguments - without treating these as an error. + + + A client binds an exchange to an exchange. The client then + repeats the bind (with identical arguments). + + + + + + A server MUST allow cycles of exchange bindings to be + created including allowing an exchange to be bound to + itself. + + + A client declares an exchange and binds it to itself. + + + + + + A server MUST not deliver the same message more than once to + a destination exchange, even if the topology of exchanges + and bindings results in multiple (even infinite) routes to + that exchange. + + + A client declares an exchange and binds it using multiple + bindings to the amq.topic exchange. The client then + publishes a message to the amq.topic exchange that matches + all the bindings. + + + + + + + + + + + + Specifies the name of the destination exchange to bind. + + + A client MUST NOT be allowed to bind a non-existent + destination exchange. + + + A client attempts to bind an undeclared exchange to an + exchange. + + + + + The server MUST accept a blank exchange name to mean the + default exchange. + + + The client declares an exchange and binds a blank exchange + name to it. + + + + + + Specifies the name of the source exchange to bind. + + + A client MUST NOT be allowed to bind a non-existent source + exchange. + + + A client attempts to bind an exchange to an undeclared + exchange. + + + + + The server MUST accept a blank exchange name to mean the + default exchange. + + + The client declares an exchange and binds it to a blank + exchange name. + + + + + + + Specifies the routing key for the binding. The routing key + is used for routing messages depending on the exchange + configuration. Not all exchanges use a routing key - refer + to the specific exchange documentation. + + + + + + + + A set of arguments for the binding. The syntax and semantics + of these arguments depends on the exchange class. + + + + + + This method confirms that the bind was successful. + + + + + + + + This method unbinds an exchange from an exchange. + + If a unbind fails, the server MUST raise a connection exception. + + + + + + + + + Specifies the name of the destination exchange to unbind. + + + The client MUST NOT attempt to unbind an exchange that + does not exist from an exchange. + + + The client attempts to unbind a non-existent exchange from + an exchange. + + + + + The server MUST accept a blank exchange name to mean the + default exchange. + + + The client declares an exchange, binds a blank exchange + name to it, and then unbinds a blank exchange name from + it. + + + + + + Specifies the name of the source exchange to unbind. + + + The client MUST NOT attempt to unbind an exchange from an + exchange that does not exist. + + + The client attempts to unbind an exchange from a + non-existent exchange. + + + + + The server MUST accept a blank exchange name to mean the + default exchange. + + + The client declares an exchange, binds an exchange to a + blank exchange name, and then unbinds an exchange from a + black exchange name. + + + + + + Specifies the routing key of the binding to unbind. + + + + + + Specifies the arguments of the binding to unbind. + + + + + This method confirms that the unbind was successful. + + + + + + + + + + Queues store and forward messages. Queues can be configured in the server or created at + runtime. Queues must be attached to at least one exchange in order to receive messages + from publishers. + + + + queue = C:DECLARE S:DECLARE-OK + / C:BIND S:BIND-OK + / C:UNBIND S:UNBIND-OK + / C:PURGE S:PURGE-OK + / C:DELETE S:DELETE-OK + + + + + + + + + + This method creates or checks a queue. When creating a new queue the client can + specify various properties that control the durability of the queue and its + contents, and the level of sharing for the queue. + + + + + The server MUST create a default binding for a newly-declared queue to the + default exchange, which is an exchange of type 'direct' and use the queue + name as the routing key. + + + Client declares a new queue, and then without explicitly binding it to an + exchange, attempts to send a message through the default exchange binding, + i.e. publish a message to the empty exchange, with the queue name as routing + key. + + + + + + The server SHOULD support a minimum of 256 queues per virtual host and ideally, + impose no limit except as defined by available resources. + + + Client attempts to declare as many queues as it can until the server reports + an error. The resulting count must at least be 256. + + + + + + + + + + + + + The queue name MAY be empty, in which case the server MUST create a new + queue with a unique generated name and return this to the client in the + Declare-Ok method. + + + Client attempts to declare several queues with an empty name. The client then + verifies that the server-assigned names are unique and different. + + + + + Queue names starting with "amq." are reserved for pre-declared and + standardised queues. The client MAY declare a queue starting with + "amq." if the passive option is set, or the queue already exists. + + + The client attempts to declare a non-existing queue starting with + "amq." and with the passive option set to zero. + + + + + The queue name can be empty, or a sequence of these characters: + letters, digits, hyphen, underscore, period, or colon. + + + The client attempts to declare a queue with an illegal name. + + + + + + + If set, the server will reply with Declare-Ok if the queue already + exists with the same name, and raise an error if not. The client can + use this to check whether a queue exists without modifying the + server state. When set, all other method fields except name and no-wait + are ignored. A declare with both passive and no-wait has no effect. + Arguments are compared for semantic equivalence. + + + + The client MAY ask the server to assert that a queue exists without + creating the queue if not. If the queue does not exist, the server + treats this as a failure. + + + Client declares an existing queue with the passive option and expects + the server to respond with a declare-ok. Client then attempts to declare + a non-existent queue with the passive option, and the server must close + the channel with the correct reply-code. + + + + + If not set and the queue exists, the server MUST check that the + existing queue has the same values for durable, exclusive, auto-delete, + and arguments fields. The server MUST respond with Declare-Ok if the + requested queue matches these fields, and MUST raise a channel exception + if not. + + + TODO. + + + + + + + If set when creating a new queue, the queue will be marked as durable. Durable + queues remain active when a server restarts. Non-durable queues (transient + queues) are purged if/when a server restarts. Note that durable queues do not + necessarily hold persistent messages, although it does not make sense to send + persistent messages to a transient queue. + + + + The server MUST recreate the durable queue after a restart. + + + Client declares a durable queue. The server is then restarted. The client + then attempts to send a message to the queue. The message should be successfully + delivered. + + + + + The server MUST support both durable and transient queues. + + A client declares two named queues, one durable and one transient. + + + + + + + Exclusive queues may only be accessed by the current connection, and are + deleted when that connection closes. Passive declaration of an exclusive + queue by other connections are not allowed. + + + + + The server MUST support both exclusive (private) and non-exclusive (shared) + queues. + + + A client declares two named queues, one exclusive and one non-exclusive. + + + + + + The client MAY NOT attempt to use a queue that was declared as exclusive + by another still-open connection. + + + One client declares an exclusive queue. A second client on a different + connection attempts to declare, bind, consume, purge, delete, or declare + a queue of the same name. + + + + + + + If set, the queue is deleted when all consumers have finished using it. The last + consumer can be cancelled either explicitly or because its channel is closed. If + there was no consumer ever on the queue, it won't be deleted. Applications can + explicitly delete auto-delete queues using the Delete method as normal. + + + + + The server MUST ignore the auto-delete field if the queue already exists. + + + Client declares two named queues, one as auto-delete and one explicit-delete. + Client then attempts to declare the two queues using the same names again, + but reversing the value of the auto-delete field in each case. Verify that the + queues still exist with the original auto-delete flag values. + + + + + + + + + A set of arguments for the declaration. The syntax and semantics of these + arguments depends on the server implementation. + + + + + + + This method confirms a Declare method and confirms the name of the queue, essential + for automatically-named queues. + + + + + + + Reports the name of the queue. If the server generated a queue name, this field + contains that name. + + + + + + + + + Reports the number of active consumers for the queue. Note that consumers can + suspend activity (Channel.Flow) in which case they do not appear in this count. + + + + + + + + + This method binds a queue to an exchange. Until a queue is bound it will not + receive any messages. In a classic messaging model, store-and-forward queues + are bound to a direct exchange and subscription queues are bound to a topic + exchange. + + + + + A server MUST allow ignore duplicate bindings - that is, two or more bind + methods for a specific queue, with identical arguments - without treating these + as an error. + + + A client binds a named queue to an exchange. The client then repeats the bind + (with identical arguments). + + + + + + A server MUST not deliver the same message more than once to a queue, even if + the queue has multiple bindings that match the message. + + + A client declares a named queue and binds it using multiple bindings to the + amq.topic exchange. The client then publishes a message that matches all its + bindings. + + + + + + The server MUST allow a durable queue to bind to a transient exchange. + + + A client declares a transient exchange. The client then declares a named durable + queue and then attempts to bind the transient exchange to the durable queue. + + + + + + Bindings of durable queues to durable exchanges are automatically durable + and the server MUST restore such bindings after a server restart. + + + A server declares a named durable queue and binds it to a durable exchange. The + server is restarted. The client then attempts to use the queue/exchange combination. + + + + + + The server SHOULD support at least 4 bindings per queue, and ideally, impose no + limit except as defined by available resources. + + + A client declares a named queue and attempts to bind it to 4 different + exchanges. + + + + + + + + + + + + Specifies the name of the queue to bind. + + + The client MUST either specify a queue name or have previously declared a + queue on the same channel + + + The client opens a channel and attempts to bind an unnamed queue. + + + + + The client MUST NOT attempt to bind a queue that does not exist. + + + The client attempts to bind a non-existent queue. + + + + + + + + A client MUST NOT be allowed to bind a queue to a non-existent exchange. + + + A client attempts to bind an named queue to a undeclared exchange. + + + + + The server MUST accept a blank exchange name to mean the default exchange. + + + The client declares a queue and binds it to a blank exchange name. + + + + + + + Specifies the routing key for the binding. The routing key is used for routing + messages depending on the exchange configuration. Not all exchanges use a + routing key - refer to the specific exchange documentation. If the queue name + is empty, the server uses the last queue declared on the channel. If the + routing key is also empty, the server uses this queue name for the routing + key as well. If the queue name is provided but the routing key is empty, the + server does the binding with that empty routing key. The meaning of empty + routing keys depends on the exchange implementation. + + + + If a message queue binds to a direct exchange using routing key K and a + publisher sends the exchange a message with routing key R, then the message + MUST be passed to the message queue if K = R. + + + + + + + + + A set of arguments for the binding. The syntax and semantics of these arguments + depends on the exchange class. + + + + + + This method confirms that the bind was successful. + + + + + + + + This method unbinds a queue from an exchange. + + If a unbind fails, the server MUST raise a connection exception. + + + + + + + + + Specifies the name of the queue to unbind. + + + The client MUST either specify a queue name or have previously declared a + queue on the same channel + + + The client opens a channel and attempts to unbind an unnamed queue. + + + + + The client MUST NOT attempt to unbind a queue that does not exist. + + + The client attempts to unbind a non-existent queue. + + + + + + The name of the exchange to unbind from. + + + The client MUST NOT attempt to unbind a queue from an exchange that + does not exist. + + + The client attempts to unbind a queue from a non-existent exchange. + + + + + The server MUST accept a blank exchange name to mean the default exchange. + + + The client declares a queue and binds it to a blank exchange name. + + + + + + Specifies the routing key of the binding to unbind. + + + + Specifies the arguments of the binding to unbind. + + + + + This method confirms that the unbind was successful. + + + + + + + + This method removes all messages from a queue which are not awaiting + acknowledgment. + + + + + The server MUST NOT purge messages that have already been sent to a client + but not yet acknowledged. + + + + + + The server MAY implement a purge queue or log that allows system administrators + to recover accidentally-purged messages. The server SHOULD NOT keep purged + messages in the same storage spaces as the live messages since the volumes of + purged messages may get very large. + + + + + + + + + + + + Specifies the name of the queue to purge. + + + The client MUST either specify a queue name or have previously declared a + queue on the same channel + + + The client opens a channel and attempts to purge an unnamed queue. + + + + + The client MUST NOT attempt to purge a queue that does not exist. + + + The client attempts to purge a non-existent queue. + + + + + + + + + This method confirms the purge of a queue. + + + + + + Reports the number of messages purged. + + + + + + + + + This method deletes a queue. When a queue is deleted any pending messages are sent + to a dead-letter queue if this is defined in the server configuration, and all + consumers on the queue are cancelled. + + + + + The server SHOULD use a dead-letter queue to hold messages that were pending on + a deleted queue, and MAY provide facilities for a system administrator to move + these messages back to an active queue. + + + + + + + + + + + + Specifies the name of the queue to delete. + + + The client MUST either specify a queue name or have previously declared a + queue on the same channel + + + The client opens a channel and attempts to delete an unnamed queue. + + + + + The client MUST NOT attempt to delete a queue that does not exist. + + + The client attempts to delete a non-existent queue. + + + + + + + If set, the server will only delete the queue if it has no consumers. If the + queue has consumers the server does does not delete it but raises a channel + exception instead. + + + + The server MUST NOT delete a queue that has consumers on it, if the if-unused + field is true. + + + The client declares a queue, and consumes from it, then tries to delete it + setting if-unused to true. + + + + + + + If set, the server will only delete the queue if it has no messages. + + + + The server MUST NOT delete a queue that has messages on it, if the + if-empty field is true. + + + The client declares a queue, binds it and publishes some messages into it, + then tries to delete it setting if-empty to true. + + + + + + + + + This method confirms the deletion of a queue. + + + + + Reports the number of messages deleted. + + + + + + + + + The Basic class provides methods that support an industry-standard messaging model. + + + + basic = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:PUBLISH content + / S:RETURN content + / S:DELIVER content + / C:GET ( S:GET-OK content / S:GET-EMPTY ) + / C:ACK + / S:ACK + / C:REJECT + / C:NACK + / S:NACK + / C:RECOVER-ASYNC + / C:RECOVER S:RECOVER-OK + + + + + + + + The server SHOULD respect the persistent property of basic messages and + SHOULD make a best-effort to hold persistent basic messages on a reliable + storage mechanism. + + + Send a persistent message to queue, stop server, restart server and then + verify whether message is still present. Assumes that queues are durable. + Persistence without durable queues makes no sense. + + + + + + The server MUST NOT discard a persistent basic message in case of a queue + overflow. + + + Declare a queue overflow situation with persistent messages and verify that + messages do not get lost (presumably the server will write them to disk). + + + + + + The server MAY use the Channel.Flow method to slow or stop a basic message + publisher when necessary. + + + Declare a queue overflow situation with non-persistent messages and verify + whether the server responds with Channel.Flow or not. Repeat with persistent + messages. + + + + + + The server MAY overflow non-persistent basic messages to persistent + storage. + + + + + + + The server MAY discard or dead-letter non-persistent basic messages on a + priority basis if the queue size exceeds some configured limit. + + + + + + + The server MUST implement at least 2 priority levels for basic messages, + where priorities 0-4 and 5-9 are treated as two distinct levels. + + + Send a number of priority 0 messages to a queue. Send one priority 9 + message. Consume messages from the queue and verify that the first message + received was priority 9. + + + + + + The server MAY implement up to 10 priority levels. + + + Send a number of messages with mixed priorities to a queue, so that all + priority values from 0 to 9 are exercised. A good scenario would be ten + messages in low-to-high priority. Consume from queue and verify how many + priority levels emerge. + + + + + + The server MUST deliver messages of the same priority in order irrespective of + their individual persistence. + + + Send a set of messages with the same priority but different persistence + settings to a queue. Consume and verify that messages arrive in same order + as originally published. + + + + + + The server MUST support un-acknowledged delivery of Basic content, i.e. + consumers with the no-ack field set to TRUE. + + + + + + The server MUST support explicitly acknowledged delivery of Basic content, + i.e. consumers with the no-ack field set to FALSE. + + + Declare a queue and a consumer using explicit acknowledgements. Publish a + set of messages to the queue. Consume the messages but acknowledge only + half of them. Disconnect and reconnect, and consume from the queue. + Verify that the remaining messages are received. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This method requests a specific quality of service. The QoS can be specified for the + current channel or for all channels on the connection. The particular properties and + semantics of a qos method always depend on the content class semantics. Though the + qos method could in principle apply to both peers, it is currently meaningful only + for the server. + + + + + + + + The client can request that messages be sent in advance so that when the client + finishes processing a message, the following message is already held locally, + rather than needing to be sent down the channel. Prefetching gives a performance + improvement. This field specifies the prefetch window size in octets. The server + will send a message in advance if it is equal to or smaller in size than the + available prefetch size (and also falls into other prefetch limits). May be set + to zero, meaning "no specific limit", although other prefetch limits may still + apply. The prefetch-size is ignored if the no-ack option is set. + + + + The server MUST ignore this setting when the client is not processing any + messages - i.e. the prefetch size does not limit the transfer of single + messages to a client, only the sending in advance of more messages while + the client still has one or more unacknowledged messages. + + + Define a QoS prefetch-size limit and send a single message that exceeds + that limit. Verify that the message arrives correctly. + + + + + + + Specifies a prefetch window in terms of whole messages. This field may be used + in combination with the prefetch-size field; a message will only be sent in + advance if both prefetch windows (and those at the channel and connection level) + allow it. The prefetch-count is ignored if the no-ack option is set. + + + + The server may send less data in advance than allowed by the client's + specified prefetch windows but it MUST NOT send more. + + + Define a QoS prefetch-size limit and a prefetch-count limit greater than + one. Send multiple messages that exceed the prefetch size. Verify that + no more than one message arrives at once. + + + + + + + By default the QoS settings apply to the current channel only. If this field is + set, they are applied to the entire connection. + + + + + + + This method tells the client that the requested QoS levels could be handled by the + server. The requested QoS applies to all active consumers until a new QoS is + defined. + + + + + + + + + This method asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the channel they were + declared on, or until the client cancels them. + + + + + The server SHOULD support at least 16 consumers per queue, and ideally, impose + no limit except as defined by available resources. + + + Declare a queue and create consumers on that queue until the server closes the + connection. Verify that the number of consumers created was at least sixteen + and report the total number. + + + + + + + + + + + Specifies the name of the queue to consume from. + + + + + Specifies the identifier for the consumer. The consumer tag is local to a + channel, so two clients can use the same consumer tags. If this field is + empty the server will generate a unique tag. + + + + The client MUST NOT specify a tag that refers to an existing consumer. + + + Attempt to create two consumers with the same non-empty tag, on the + same channel. + + + + + The consumer tag is valid only within the channel from which the + consumer was created. I.e. a client MUST NOT create a consumer in one + channel and then use it in another. + + + Attempt to create a consumer in one channel, then use in another channel, + in which consumers have also been created (to test that the server uses + unique consumer tags). + + + + + + + + + + + Request exclusive consumer access, meaning only this consumer can access the + queue. + + + + + The client MAY NOT gain exclusive access to a queue that already has + active consumers. + + + Open two connections to a server, and in one connection declare a shared + (non-exclusive) queue and then consume from the queue. In the second + connection attempt to consume from the same queue using the exclusive + option. + + + + + + + + + A set of arguments for the consume. The syntax and semantics of these + arguments depends on the server implementation. + + + + + + + The server provides the client with a consumer tag, which is used by the client + for methods called on the consumer at a later stage. + + + + + Holds the consumer tag specified by the client or provided by the server. + + + + + + + + + This method cancels a consumer. This does not affect already delivered + messages, but it does mean the server will not send any more messages for + that consumer. The client may receive an arbitrary number of messages in + between sending the cancel method and receiving the cancel-ok reply. + + It may also be sent from the server to the client in the event + of the consumer being unexpectedly cancelled (i.e. cancelled + for any reason other than the server receiving the + corresponding basic.cancel from the client). This allows + clients to be notified of the loss of consumers due to events + such as queue deletion. Note that as it is not a MUST for + clients to accept this method from the client, it is advisable + for the broker to be able to identify those clients that are + capable of accepting the method, through some means of + capability negotiation. + + + + + If the queue does not exist the server MUST ignore the cancel method, so + long as the consumer tag is valid for that channel. + + + TODO. + + + + + + + + + + + + + + This method confirms that the cancellation was completed. + + + + + + + + + + + This method publishes a message to a specific exchange. The message will be routed + to queues as defined by the exchange configuration and distributed to any active + consumers when the transaction, if any, is committed. + + + + + + + + + + Specifies the name of the exchange to publish to. The exchange name can be + empty, meaning the default exchange. If the exchange name is specified, and that + exchange does not exist, the server will raise a channel exception. + + + + + The client MUST NOT attempt to publish a content to an exchange that + does not exist. + + + The client attempts to publish a content to a non-existent exchange. + + + + + The server MUST accept a blank exchange name to mean the default exchange. + + + The client declares a queue and binds it to a blank exchange name. + + + + + If the exchange was declared as an internal exchange, the server MUST raise + a channel exception with a reply code 403 (access refused). + + + TODO. + + + + + + The exchange MAY refuse basic content in which case it MUST raise a channel + exception with reply code 540 (not implemented). + + + TODO. + + + + + + + Specifies the routing key for the message. The routing key is used for routing + messages depending on the exchange configuration. + + + + + + This flag tells the server how to react if the message cannot be routed to a + queue. If this flag is set, the server will return an unroutable message with a + Return method. If this flag is zero, the server silently drops the message. + + + + + The server SHOULD implement the mandatory flag. + + + TODO. + + + + + + + This flag tells the server how to react if the message cannot be routed to a + queue consumer immediately. If this flag is set, the server will return an + undeliverable message with a Return method. If this flag is zero, the server + will queue the message, but with no guarantee that it will ever be consumed. + + + + + The server SHOULD implement the immediate flag. + + + TODO. + + + + + + + + This method returns an undeliverable message that was published with the "immediate" + flag set, or an unroutable message published with the "mandatory" flag set. The + reply code and text provide information about the reason that the message was + undeliverable. + + + + + + + + + + Specifies the name of the exchange that the message was originally published + to. May be empty, meaning the default exchange. + + + + + + Specifies the routing key name specified when the message was published. + + + + + + + + + This method delivers a message to the client, via a consumer. In the asynchronous + message delivery model, the client starts a consumer using the Consume method, then + the server responds with Deliver methods as and when messages arrive for that + consumer. + + + + + The server SHOULD track the number of times a message has been delivered to + clients and when a message is redelivered a certain number of times - e.g. 5 + times - without being acknowledged, the server SHOULD consider the message to be + unprocessable (possibly causing client applications to abort), and move the + message to a dead letter queue. + + + TODO. + + + + + + + + + + + + Specifies the name of the exchange that the message was originally published to. + May be empty, indicating the default exchange. + + + + + Specifies the routing key name specified when the message was published. + + + + + + + + This method provides a direct access to the messages in a queue using a synchronous + dialogue that is designed for specific types of application where synchronous + functionality is more important than performance. + + + + + + + + + + + Specifies the name of the queue to get a message from. + + + + + + + This method delivers a message to the client following a get method. A message + delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the + get method. + + + + + + + + + Specifies the name of the exchange that the message was originally published to. + If empty, the message was published to the default exchange. + + + + + Specifies the routing key name specified when the message was published. + + + + + + + + This method tells the client that the queue has no messages available for the + client. + + + + + + + + + + + When sent by the client, this method acknowledges one or more + messages delivered via the Deliver or Get-Ok methods. + + When sent by server, this method acknowledges one or more + messages published with the Publish method on a channel in + confirm mode. + + The acknowledgement can be for a single message or a set of + messages up to and including a specific message. + + + + + + + + + If set to 1, the delivery tag is treated as "up to and + including", so that multiple messages can be acknowledged + with a single method. If set to zero, the delivery tag + refers to a single message. If the multiple field is 1, and + the delivery tag is zero, this indicates acknowledgement of + all outstanding messages. + + + + A message MUST not be acknowledged more than once. The + receiving peer MUST validate that a non-zero delivery-tag + refers to a delivered message, and raise a channel + exception if this is not the case. On a transacted + channel, this check MUST be done immediately and not + delayed until a Tx.Commit. + + + TODO. + + + + + + + + + + This method allows a client to reject a message. It can be used to interrupt and + cancel large incoming messages, or return untreatable messages to their original + queue. + + + + + The server SHOULD be capable of accepting and process the Reject method while + sending message content with a Deliver or Get-Ok method. I.e. the server should + read and process incoming methods while sending output frames. To cancel a + partially-send content, the server sends a content body frame of size 1 (i.e. + with no data except the frame-end octet). + + + + + + The server SHOULD interpret this method as meaning that the client is unable to + process the message at this time. + + + TODO. + + + + + + The client MUST NOT use this method as a means of selecting messages to process. + + + TODO. + + + + + + + + + + If requeue is true, the server will attempt to requeue the message. If requeue + is false or the requeue attempt fails the messages are discarded or dead-lettered. + + + + + The server MUST NOT deliver the message to the same client within the + context of the current channel. The recommended strategy is to attempt to + deliver the message to an alternative consumer, and if that is not possible, + to move the message to a dead-letter queue. The server MAY use more + sophisticated tracking to hold the message on the queue and redeliver it to + the same client at a later stage. + + + TODO. + + + + + + + + + + This method asks the server to redeliver all unacknowledged messages on a + specified channel. Zero or more messages may be redelivered. This method + is deprecated in favour of the synchronous Recover/Recover-Ok. + + + + The server MUST set the redelivered flag on all messages that are resent. + + + TODO. + + + + + + If this field is zero, the message will be redelivered to the original + recipient. If this bit is 1, the server will attempt to requeue the message, + potentially then delivering it to an alternative subscriber. + + + + + + + + + This method asks the server to redeliver all unacknowledged messages on a + specified channel. Zero or more messages may be redelivered. This method + replaces the asynchronous Recover. + + + + The server MUST set the redelivered flag on all messages that are resent. + + + TODO. + + + + + + If this field is zero, the message will be redelivered to the original + recipient. If this bit is 1, the server will attempt to requeue the message, + potentially then delivering it to an alternative subscriber. + + + + + + + This method acknowledges a Basic.Recover method. + + + + + + + This method allows a client to reject one or more incoming messages. It can be + used to interrupt and cancel large incoming messages, or return untreatable + messages to their original queue. + + This method is also used by the server to inform publishers on channels in + confirm mode of unhandled messages. If a publisher receives this method, it + probably needs to republish the offending messages. + + + + + The server SHOULD be capable of accepting and processing the Nack method while + sending message content with a Deliver or Get-Ok method. I.e. the server should + read and process incoming methods while sending output frames. To cancel a + partially-send content, the server sends a content body frame of size 1 (i.e. + with no data except the frame-end octet). + + + + + + The server SHOULD interpret this method as meaning that the client is unable to + process the message at this time. + + + TODO. + + + + + + The client MUST NOT use this method as a means of selecting messages to process. + + + TODO. + + + + + + A client publishing messages to a channel in confirm mode SHOULD be capable of accepting + and somehow handling the Nack method. + + + TODO + + + + + + + + + + + If set to 1, the delivery tag is treated as "up to and + including", so that multiple messages can be rejected + with a single method. If set to zero, the delivery tag + refers to a single message. If the multiple field is 1, and + the delivery tag is zero, this indicates rejection of + all outstanding messages. + + + + A message MUST not be rejected more than once. The + receiving peer MUST validate that a non-zero delivery-tag + refers to an unacknowledged, delivered message, and + raise a channel exception if this is not the case. + + + TODO. + + + + + + + If requeue is true, the server will attempt to requeue the message. If requeue + is false or the requeue attempt fails the messages are discarded or dead-lettered. + Clients receiving the Nack methods should ignore this flag. + + + + + The server MUST NOT deliver the message to the same client within the + context of the current channel. The recommended strategy is to attempt to + deliver the message to an alternative consumer, and if that is not possible, + to move the message to a dead-letter queue. The server MAY use more + sophisticated tracking to hold the message on the queue and redeliver it to + the same client at a later stage. + + + TODO. + + + + + + + + + + + + The Tx class allows publish and ack operations to be batched into atomic + units of work. The intention is that all publish and ack requests issued + within a transaction will complete successfully or none of them will. + Servers SHOULD implement atomic transactions at least where all publish + or ack requests affect a single queue. Transactions that cover multiple + queues may be non-atomic, given that queues can be created and destroyed + asynchronously, and such events do not form part of any transaction. + Further, the behaviour of transactions with respect to the immediate and + mandatory flags on Basic.Publish methods is not defined. + + + + + Applications MUST NOT rely on the atomicity of transactions that + affect more than one queue. + + + + + Applications MUST NOT rely on the behaviour of transactions that + include messages published with the immediate option. + + + + + Applications MUST NOT rely on the behaviour of transactions that + include messages published with the mandatory option. + + + + + tx = C:SELECT S:SELECT-OK + / C:COMMIT S:COMMIT-OK + / C:ROLLBACK S:ROLLBACK-OK + + + + + + + + + + This method sets the channel to use standard transactions. The client must use this + method at least once on a channel before using the Commit or Rollback methods. + + + + + + + + This method confirms to the client that the channel was successfully set to use + standard transactions. + + + + + + + + + This method commits all message publications and acknowledgments performed in + the current transaction. A new transaction starts immediately after a commit. + + + + + + + The client MUST NOT use the Commit method on non-transacted channels. + + + The client opens a channel and then uses Tx.Commit. + + + + + + + This method confirms to the client that the commit succeeded. Note that if a commit + fails, the server raises a channel exception. + + + + + + + + + This method abandons all message publications and acknowledgments performed in + the current transaction. A new transaction starts immediately after a rollback. + Note that unacked messages will not be automatically redelivered by rollback; + if that is required an explicit recover call should be issued. + + + + + + + The client MUST NOT use the Rollback method on non-transacted channels. + + + The client opens a channel and then uses Tx.Rollback. + + + + + + + This method confirms to the client that the rollback succeeded. Note that if an + rollback fails, the server raises a channel exception. + + + + + + + + + + The Confirm class allows publishers to put the channel in + confirm mode and susequently be notified when messages have been + handled by the broker. The intention is that all messages + published on a channel in confirm mode will be acknowledged at + some point. By acknowledging a message the broker assumes + responsibility for it and indicates that it has done something + it deems reasonable with it. + + Unroutable mandatory or immediate messages are acknowledged + right after the Basic.Return method. Messages are acknowledged + when all queues to which the message has been routed + have either delivered the message and received an + acknowledgement (if required), or enqueued the message (and + persisted it if required). + + Published messages are assigned ascending sequence numbers, + starting at 1 with the first Confirm.Select method. The server + confirms messages by sending Basic.Ack methods referring to these + sequence numbers. + + + + + The server MUST acknowledge all messages received after the + channel was put into confirm mode. + + + + + + The server MUST acknowledge a message only after it was + properly handled by all the queues it was delivered to. + + + + + + The server MUST acknowledge an unroutable mandatory or + immediate message only after it sends the Basic.Return. + + + + + + No guarantees are made as to how soon a message is + acknowledged. Applications SHOULD NOT make assumptions about + this. + + + + + confirm = C:SELECT S:SELECT-OK + + + + + + + + + select confirm mode (i.e. enable publisher acknowledgements) + + This method sets the channel to use publisher acknowledgements. + The client can only use this method on a non-transactional + channel. + + + + + do not send a reply method + + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + + + + + + acknowledge confirm mode + + This method confirms to the client that the channel was successfully + set to use publisher acknowledgements. + + + + + + From a31379e003651123b28b057d041a59c9cee97859 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 13 Sep 2012 18:39:28 -0700 Subject: [PATCH 024/165] finish on_return at message, channel, and connection level --- lib/AnyEvent/RabbitMQ.pm | 10 +++-- lib/AnyEvent/RabbitMQ/Channel.pm | 66 +++++++++++++++++++------------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 99b7065..e0f9114 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -167,8 +167,8 @@ sub _read_loop { my ($frame) = Net::AMQP->parse_raw_frames(\$stack); if ($self->{verbose}) { - warn '[C] <-- [S] ' . Dumper($frame); - warn '-----------', "\n"; + warn '[C] <-- [S] ', Dumper($frame), + '-----------', "\n"; } my $id = $frame->channel; @@ -583,6 +583,10 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. }, on_failure => $cv, on_read_failure => sub {die @_}, + on_return => sub { + my $frame = shift; + die "Unable to deliver ", Dumper($frame); + } on_close => sub { my $method_frame = shift->method_frame; die $method_frame->reply_code, $method_frame->reply_text; @@ -599,7 +603,7 @@ You can use AnyEvent::RabbitMQ to - * Declare and delete exchanges * Declare, delete, bind and unbind queues - * Set QoS + * Set QoS and confirm mode * Publish, consume, get, ack, recover and reject messages * Select, commit and rollback transactions diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index f3390e2..f542676 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -3,8 +3,9 @@ package AnyEvent::RabbitMQ::Channel; use strict; use warnings; -use Scalar::Util qw(weaken); use AnyEvent::RabbitMQ::LocalQueue; +use Scalar::Util qw(weaken); +use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } our $VERSION = '1.08'; @@ -311,10 +312,24 @@ sub publish { my $header_args = delete $args{header}; my $body = delete $args{body}; my $ack_cb = delete $args{on_ack}; + my $return_cb = delete $args{on_return}; defined($header_args) or $header_args = {}; defined($body) or $body = ''; - defined($ack_cb) or $ack_cb = sub {}; + defined($ack_cb) or defined($return_cb) + and !$self->{_is_confirm} + and croak "Can't set on_ack or on_return callback when not in confirm mode"; + + my $tag; + if ($self->{_is_confirm}) { + # yeah, delivery tags in acks are sequential. see Java client + $tag = ++$self->{_publish_tag}; + if ($return_cb) { + $header_args = { %$header_args }; + $header_args->{headers}{_return} = $tag; # just reuse the same value, why not + } + $self->{_publish_cbs}{$tag} = [$ack_cb, $return_cb]; + } $self->_publish( %args, @@ -324,16 +339,6 @@ sub publish { $body, ); - if ($self->{_is_confirm}) { - # yeah, they're sequential. see Java client - my $tag = ++$self->{_publish_tag}; - $self->{_publish_cbs}{$tag} = $ack_cb; - } - else { - # we do not expect an ack, so assume success - $ack_cb->(); - } - return $self; } @@ -356,11 +361,13 @@ sub _publish { } sub _header { - my ($self, $args, $body,) = @_; + my ($self, $args, $body) = @_; + + my $weight = delete $args->{weight} || 0; $self->{connection}->_push_write( Net::AMQP::Frame::Header->new( - weight => $args->{weight} || 0, + weight => $weight, body_size => length($body), header_frame => Net::AMQP::Protocol::Basic::ContentHeader->new( content_type => 'application/octet-stream', @@ -545,19 +552,19 @@ sub confirm { weaken(my $wself = $self); $self->{connection}->_push_write_and_read( - 'Confirm::Select', - { - %args, - nowait => 0, # FIXME - }, - 'Confirm::SelectOk', + 'Confirm::Select', + { + %args, + nowait => 0, # FIXME + }, + 'Confirm::SelectOk', sub { my $me = $wself or return; $me->{_is_confirm} = 1; $cb->(); }, - $failure_cb, - $self->{id}, + $failure_cb, + $self->{id}, ); return $self; @@ -678,19 +685,26 @@ sub push_queue_or_consume { $self->_push_read_header_and_body('deliver', $frame, $cb, $failure_cb); return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Return')) { - my $cb = $self->{on_return} || $failure_cb; + my $cb = sub { + my $ret = shift; + my $headers = $ret->{header}->headers || {}; + my $tag = $headers->{_return_tag}; + my $cbs = $self->{_publish_cbs}{$headers->{_return}}; + my $onret_cb = ($cbs && $cbs->[1]) || $self->{on_return} || $self->{connection}{on_return} || sub {}; # oh well + $onret_cb->($frame); + }; $self->_push_read_header_and_body('return', $frame, $cb, $failure_cb); return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Ack')) { - my $pub_cb; + my $cbs; if (!$self->{_is_confirm}) { $failure_cb->("Received Ack when not in confirm mode"); } - elsif (not $pub_cb = delete $self->{_publish_cbs}{$method_frame->{delivery_tag}}) { + elsif (not $cbs = delete $self->{_publish_cbs}{$method_frame->{delivery_tag}}) { $failure_cb->("Received Ack of unknown delivery tag $method_frame->{delivery_tag}"); } else { - $pub_cb->(); + $cbs->[0]->(); } } elsif ($method_frame->isa('Net::AMQP::Protocol::Channel::Flow')) { $self->{_is_active} = $method_frame->active; From b867a9b226978034988595df1b7fc25fd92e2852 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Tue, 18 Sep 2012 13:28:42 -0700 Subject: [PATCH 025/165] Handle flow control (do not throw messages away!) Handle out-of-order CancelOk Handle server-sent Nack Add missing accessors, e.g. is_open and is_active and verbose Fix a potential memory leak --- lib/AnyEvent/RabbitMQ.pm | 12 +++- lib/AnyEvent/RabbitMQ/Channel.pm | 106 +++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 28 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index e0f9114..25169cb 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -47,6 +47,16 @@ sub new { }, $class; } +sub verbose { + my $self = shift; + @_ ? ($self->{verbose} = shift) : $self->{verbose} +} + +sub is_open { + my $self = shift; + $self->{_is_open} +} + sub channels { my $self = shift; return $self->{_channels}; @@ -55,7 +65,7 @@ sub channels { sub delete_channel { my $self = shift; my ($id) = @_; - return delete $self->{_channels}->{$id}; + return defined delete $self->{_channels}->{$id}; } sub login_user { diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index f542676..da0b1dd 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -14,7 +14,7 @@ sub new { my $class = shift; my $self = bless { - @_, # id, connection, on_return, on_close + @_, # id, connection, on_return, on_close, on_inactive, on_active _queue => AnyEvent::RabbitMQ::LocalQueue->new, _content_queue => AnyEvent::RabbitMQ::LocalQueue->new, }, $class; @@ -32,12 +32,28 @@ sub _reset { _publish_tag => 0, _publish_cbs => {}, _consumer_cbs => {}, + _consumer_cans => {}, ); @$self{keys %a} = values %a; return $self; } +sub is_open { + my $self = shift; + return $self->{_is_open}; +} + +sub is_active { + my $self = shift; + return $self->{_is_active}; +} + +sub is_confirm { + my $self = shift; + return $self->{_is_confirm}; +} + sub queue { my $self = shift; return $self->{_queue}; @@ -123,8 +139,10 @@ sub _close { my $on_close = $self->{on_close}; $self->{_is_open} = 0; - $self->{_queue}->_flush($frame); - $self->{_content_queue}->_flush($frame); + if ($frame) { + $self->{_queue}->_flush($frame); + $self->{_content_queue}->_flush($frame); + } $self->_reset; $connection->delete_channel($self->{id}) if $connection; @@ -132,7 +150,7 @@ sub _close { if (defined $on_close) { local $@; $on_close->($frame); - warn "Error in callback, ignored:\n $@ " if $@; + warn "Error in channel on_close callback, ignored:\n $@ " if $@; } return $self; @@ -307,18 +325,26 @@ sub publish { my $self = shift; my %args = @_; - return $self if !$self->{_is_active}; + # Docs should advise channel-level callback over this, but still, better to give user an out + unless ($self->{_is_active}) { + if (defined $args{on_inactive}) { + $args{on_inactive}->(); + return $self; + } + croak "Can't publish on inactive channel (server flow control); provide on_inactive callback"; + } my $header_args = delete $args{header}; my $body = delete $args{body}; my $ack_cb = delete $args{on_ack}; + my $nack_cb = delete $args{on_nack}; my $return_cb = delete $args{on_return}; defined($header_args) or $header_args = {}; defined($body) or $body = ''; - defined($ack_cb) or defined($return_cb) + defined($ack_cb) or defined($nack_cb) or defined($return_cb) and !$self->{_is_confirm} - and croak "Can't set on_ack or on_return callback when not in confirm mode"; + and croak "Can't set on_ack/on_nack/on_return callback when not in confirm mode"; my $tag; if ($self->{_is_confirm}) { @@ -328,7 +354,7 @@ sub publish { $header_args = { %$header_args }; $header_args->{headers}{_return} = $tag; # just reuse the same value, why not } - $self->{_publish_cbs}{$tag} = [$ack_cb, $return_cb]; + $self->{_publish_cbs}{$tag} = [$ack_cb, $nack_cb, $return_cb]; } $self->_publish( @@ -453,19 +479,13 @@ sub cancel { return $self; } - $self->{connection}->_push_write_and_read( - 'Basic::Cancel', - { + $self->{_consumer_cans}{$args{consumer_tag}} = $cb; + + $self->{connection}->_push_write( + Net::AMQP::Protocol::Basic::Cancel->new( %args, # consumer_tag nowait => 0, - }, - 'Basic::CancelOk', - sub { - my $frame = shift; - delete $self->{_consumer_cbs}->{$args{consumer_tag}}; - $cb->($frame); - }, - $failure_cb, + ), $self->{id}, ); @@ -684,28 +704,57 @@ sub push_queue_or_consume { } || sub {}; $self->_push_read_header_and_body('deliver', $frame, $cb, $failure_cb); return $self; + } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::CancelOk')) { + my $can_cb = delete $self->{_consumer_cans}{$method_frame->consumer_tag}; + if ($can_cb) { + $can_cb->($method_frame); + } + else { + $failure_cb->("Received CancelOk for unknown consumer tag " . $method_frame->consumer_tag); + } + return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Return')) { + weaken(my $wself = $self); my $cb = sub { my $ret = shift; + my $me = $wself or return; my $headers = $ret->{header}->headers || {}; my $tag = $headers->{_return_tag}; - my $cbs = $self->{_publish_cbs}{$headers->{_return}}; - my $onret_cb = ($cbs && $cbs->[1]) || $self->{on_return} || $self->{connection}{on_return} || sub {}; # oh well + my $cbs = $me->{_publish_cbs}{$headers->{_return}}; + my $onret_cb = ($cbs && $cbs->[1]) || $me->{on_return} || $me->{connection}{on_return} || sub {}; # oh well $onret_cb->($frame); }; $self->_push_read_header_and_body('return', $frame, $cb, $failure_cb); return $self; - } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Ack')) { + } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Ack') || + $method_frame->isa('Net::AMQP::Protocol::Basic::Nack')) { + (my $resp = ref($method_frame)) =~ s/.*:://; my $cbs; if (!$self->{_is_confirm}) { - $failure_cb->("Received Ack when not in confirm mode"); - } - elsif (not $cbs = delete $self->{_publish_cbs}{$method_frame->{delivery_tag}}) { - $failure_cb->("Received Ack of unknown delivery tag $method_frame->{delivery_tag}"); + $failure_cb->("Received $resp when not in confirm mode"); } else { - $cbs->[0]->(); + my @tags; + if ($method_frame->{multiple}) { + @tags = sort { $a <=> $b } + grep { $_ <= $method_frame->{delivery_tag} } + keys %{$self->{_publish_cbs}}; + } + else { + @tags = ($method_frame->{delivery_tag}); + } + my $cbi = ($resp eq 'Ack') ? 0 : 1; + for my $tag (@tags) { + my $cbs; + if (not $cbs = delete $self->{_publish_cbs}{$tag}) { + $failure_cb->("Received $resp of unknown delivery tag $tag"); + } + elsif ($cbs->[$cbi]) { + $cbs->[$cbi]->(); + } + } } + return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Channel::Flow')) { $self->{_is_active} = $method_frame->active; $self->{connection}->_push_write( @@ -714,6 +763,9 @@ sub push_queue_or_consume { ), $self->{id}, ); + my $cbname = $self->{_is_active} ? 'on_active' : 'on_inactive'; + my $cb = $self->{$cbname} || $self->{connection}{$cbname} || sub {}; + $cb->($frame); return $self; } $self->{_queue}->push($frame); From fccb0da8ad152151f4e32878402674fb2a4336ea Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Tue, 18 Sep 2012 13:29:59 -0700 Subject: [PATCH 026/165] Update tests to work with all new features, and test some of them. --- xt/04_anyevent.t | 80 +++++++++++++++++++++++++++++++------------ xt/05_multi_channel.t | 2 +- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index 205129a..515d80a 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -16,7 +16,7 @@ my %conf = ( user => 'guest', pass => 'guest', vhost => '/', - verbose => 1, + #verbose => 1, ); eval { @@ -34,7 +34,7 @@ eval { plan skip_all => 'Connection failure: ' . $conf{host} . ':' . $conf{port} if $@; -plan tests => 31; +plan tests => 35; use AnyEvent::RabbitMQ; @@ -56,31 +56,41 @@ $ar->connect( $done->send; }, on_failure => failure_cb($done), - on_close => sub { + on_return => sub { my $method_frame = shift->method_frame; - die $method_frame->reply_code, $method_frame->reply_text + die "return: ", $method_frame->reply_code, $method_frame->reply_text if $method_frame->reply_code; }, -); -$done->recv; - -$done = AnyEvent->condvar; -my $ch; -$ar->open_channel( - on_success => sub { - $ch = shift; - isa_ok($ch, 'AnyEvent::RabbitMQ::Channel'); - $done->send; - }, - on_failure => failure_cb($done), on_close => sub { my $method_frame = shift->method_frame; - die $method_frame->reply_code, $method_frame->reply_text + die "close: ", $method_frame->reply_code, $method_frame->reply_text if $method_frame->reply_code; }, ); $done->recv; +my $ch; +$done = AnyEvent->condvar; +open_ch($done); +$done->recv; + +sub open_ch { + my ($cv,) = @_; + $ar->open_channel( + on_success => sub { + $ch = shift; + isa_ok($ch, 'AnyEvent::RabbitMQ::Channel'); + $cv->send; + }, + on_failure => failure_cb($cv), + on_close => sub { + my $method_frame = shift->method_frame; + die $method_frame->reply_code, $method_frame->reply_text + if $method_frame->reply_code; + }, + ); +} + $done = AnyEvent->condvar; $ch->declare_exchange( exchange => 'test_x', @@ -296,7 +306,16 @@ pass('recover'); # This only works for RabbitMQ >= 2.0.0 my $can_reject = $server{product} eq 'RabbitMQ' && $server{version} >= version->parse('2.0.0'); SKIP: { - skip 'We need RabbitMQ >= 2.0.0 for the reject test', 1 unless $can_reject; + skip 'We need RabbitMQ >= 2.0.0 for the confirm and reject test', 1 unless $can_reject; + + $done = AnyEvent->condvar; + $ch->confirm( + on_success => sub { $done->send }, + on_failure => failure_cb($done), + ); + $done->recv; + pass('confirm'); + $done = AnyEvent->condvar; my $reject_count = 0; $ch->consume( @@ -327,9 +346,28 @@ SKIP: { }, on_failure => failure_cb($done), ); - publish($ch, 'RabbitMQ is powerful.', $done,); + my $pub_done = AnyEvent->condvar; + publish($ch, 'RabbitMQ is powerful.', $pub_done,); + $pub_done->recv; $done->recv; pass('reject'); + + # reopen because confirm is not compatible with transactions + $done = AnyEvent->condvar; + $ch->close( + on_success => sub { + pass('close2'); + $done->send; + }, + on_failure => failure_cb($done), + ); + $done->recv; + undef $ch; + + $done = AnyEvent->condvar; + open_ch($done); + $done->recv; + pass('open2'); }; $done = AnyEvent->condvar; @@ -407,7 +445,7 @@ $done->recv; $done = AnyEvent->condvar; $ar->close( on_success => sub { - pass('close'); + pass('close2'); $done->send; }, on_failure => failure_cb($done), @@ -429,6 +467,7 @@ sub publish { exchange => 'test_x', routing_key => 'test_r', body => $message, + on_ack => sub { $cv->send }, on_return => sub { my $response = shift; fail('on_return: ' . Dumper($response)); @@ -454,7 +493,6 @@ sub send_large_size_message { on_failure => failure_cb($done), ); $done->recv; - return; } diff --git a/xt/05_multi_channel.t b/xt/05_multi_channel.t index 774129b..2c37b11 100644 --- a/xt/05_multi_channel.t +++ b/xt/05_multi_channel.t @@ -97,6 +97,7 @@ sub open_channel { $ar->open_channel( on_success => sub {$done->send(shift)}, on_failure => sub {$done->send()}, + on_return => sub {die 'Receive return'}, on_close => \&handle_close, ); my $ch = $done->recv; @@ -155,7 +156,6 @@ sub publish { routing_key => $queue, body => $message, mandatory => 1, - on_return => sub {die 'Receive return'}, ); return; From 2ca721918ed7f14a30e9bfe3f43d5e3badb22527 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Tue, 18 Sep 2012 13:30:16 -0700 Subject: [PATCH 027/165] Document spec xml files --- share/README | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 share/README diff --git a/share/README b/share/README new file mode 100644 index 0000000..2731ca7 --- /dev/null +++ b/share/README @@ -0,0 +1,3 @@ +AMQP spec files +fixed_amqp0-8.xml - standard 0.8 spec plus Rabbit extensions +fixed_amqp0-9-1.xml - standard 0.9.1 spec plus Rabbit extensions From 96ffbcda70d64d72f968490c4470268d37257cbc Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 20 Sep 2012 19:39:46 -0700 Subject: [PATCH 028/165] fix on_return for non-confirm publish --- lib/AnyEvent/RabbitMQ/Channel.pm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index da0b1dd..17c1773 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -30,7 +30,7 @@ sub _reset { _is_active => 0, _is_confirm => 0, _publish_tag => 0, - _publish_cbs => {}, + _publish_cbs => {}, # values: [on_ack, on_nack, on_return] _consumer_cbs => {}, _consumer_cans => {}, ); @@ -352,7 +352,7 @@ sub publish { $tag = ++$self->{_publish_tag}; if ($return_cb) { $header_args = { %$header_args }; - $header_args->{headers}{_return} = $tag; # just reuse the same value, why not + $header_args->{headers}{_ar_return} = $tag; # just reuse the same value, why not } $self->{_publish_cbs}{$tag} = [$ack_cb, $nack_cb, $return_cb]; } @@ -719,9 +719,12 @@ sub push_queue_or_consume { my $ret = shift; my $me = $wself or return; my $headers = $ret->{header}->headers || {}; - my $tag = $headers->{_return_tag}; - my $cbs = $me->{_publish_cbs}{$headers->{_return}}; - my $onret_cb = ($cbs && $cbs->[1]) || $me->{on_return} || $me->{connection}{on_return} || sub {}; # oh well + my $onret_cb; + if (defined(my $tag = $headers->{_ar_return})) { + my $cbs = delete $me->{_publish_cbs}{$tag}; + $onret_cb = $cbs->[2] if $cbs; + } + $onret_cb ||= $me->{on_return} || $me->{connection}{on_return} || sub {}; # oh well $onret_cb->($frame); }; $self->_push_read_header_and_body('return', $frame, $cb, $failure_cb); From a66a809f6438caf18ddcbfde96ad51ff6903d2c9 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 11:50:53 +0000 Subject: [PATCH 029/165] Fix spelling test --- xt/01_podspell.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xt/01_podspell.t b/xt/01_podspell.t index 1dc139d..cac0fc4 100644 --- a/xt/01_podspell.t +++ b/xt/01_podspell.t @@ -6,6 +6,8 @@ set_spell_cmd('aspell list'); $ENV{LANG} = 'C'; all_pod_files_spelling_ok('lib'); __DATA__ +signalled +cancelled Masahito Ikuta cooldaemon@gmail.com AMQP From 192b417742ed52ae1656dec325e46059f74d30bb Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 11:51:07 +0000 Subject: [PATCH 030/165] Fix use of a condvar in the close method --- lib/AnyEvent/RabbitMQ.pm | 46 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 25169cb..1fab2a9 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -7,6 +7,7 @@ use List::MoreUtils qw(none); use Devel::GlobalDestruction; use File::ShareDir; use Readonly; +use Scalar::Util qw/ weaken /; use namespace::clean; require Data::Dumper; @@ -308,7 +309,7 @@ sub _open { virtual_host => $args{vhost}, insist => 1, }, - 'Connection::OpenOk', + 'Connection::OpenOk', sub { $self->{_is_open} = 1; $self->{_login_user} = $args{user}; @@ -322,6 +323,8 @@ sub _open { sub close { my $self = shift; + my $weak_self = $self; + weaken($weak_self); my %args = $self->_set_cbs(@_); if (!$self->{_is_open}) { @@ -329,40 +332,39 @@ sub close { return $self; } - my $channels_cv = AnyEvent->condvar; - $channels_cv->begin( - sub { - $self->_close( - sub { - $self->_disconnect(); - $args{on_success}->(@_); - }, - sub { - $self->_disconnect(); - $args{on_failure}->(@_); - } - ); - } - ); - + my $channels_to_close = 0; + my $all_closed_cb = sub { + return unless 0 == $channels_to_close; + $weak_self->_close( + sub { + $weak_self->_disconnect(); + $args{on_success}->(@_); + }, + sub { + $weak_self->_disconnect(); + $args{on_failure}->(@_); + } + ); + }; for my $id (keys %{$self->{_channels}}) { my $channel = $self->{_channels}->{$id} or next; # Could have already gone away on global destruction.. - $channels_cv->begin; + $channels_to_close++; + $channel->close( on_success => sub { - $channels_cv->end; + $channels_to_close--; + $all_closed_cb->(); }, on_failure => sub { - $channels_cv->end; + $channels_to_close--; + $all_closed_cb->(); $args{on_failure}->(@_); }, ); } - $channels_cv->end; - return $self; } From 5902f389692559d54df9c6ae66caa9a723122a2f Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 11:52:29 +0000 Subject: [PATCH 031/165] Fix spacing/tabs --- xt/04_anyevent.t | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index 515d80a..c2169f7 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -77,7 +77,7 @@ $done->recv; sub open_ch { my ($cv,) = @_; $ar->open_channel( - on_success => sub { + on_success => sub { $ch = shift; isa_ok($ch, 'AnyEvent::RabbitMQ::Channel'); $cv->send; @@ -310,8 +310,8 @@ SKIP: { $done = AnyEvent->condvar; $ch->confirm( - on_success => sub { $done->send }, - on_failure => failure_cb($done), + on_success => sub { $done->send }, + on_failure => failure_cb($done), ); $done->recv; pass('confirm'); @@ -477,7 +477,7 @@ sub publish { return; } - + sub send_large_size_message { my ($ch, $size,) = @_; From 03f47302116f5f5d6f648cc7d5b8e6b6c6bfe535 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 11:59:54 +0000 Subject: [PATCH 032/165] Fix test --- xt/04_anyevent.t | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index c2169f7..40c6472 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -3,7 +3,6 @@ use Test::Exception; use Data::Dumper; use FindBin; -use version; my %server = ( product => undef, @@ -16,7 +15,7 @@ my %conf = ( user => 'guest', pass => 'guest', vhost => '/', - #verbose => 1, +# verbose => 1, ); eval { @@ -34,7 +33,6 @@ eval { plan skip_all => 'Connection failure: ' . $conf{host} . ':' . $conf{port} if $@; -plan tests => 35; use AnyEvent::RabbitMQ; @@ -63,7 +61,7 @@ $ar->connect( }, on_close => sub { my $method_frame = shift->method_frame; - die "close: ", $method_frame->reply_code, $method_frame->reply_text + Carp::confess "close: ", $method_frame->reply_code, $method_frame->reply_text if $method_frame->reply_code; }, ); @@ -181,7 +179,7 @@ $ch->get( ); $done->recv; -for my $size (10, 131_064, 200_000, 10, 999_999, 10) { +for my $size (10, 131_064, 10) { send_large_size_message($ch, $size); } @@ -487,7 +485,7 @@ sub send_large_size_message { queue => 'test_q', on_success => sub { my $response = shift; - is(length($response->{body}->payload), $size, 'get large size'); + is(length($response->{body}->payload), $size, 'get large size: ' . $size); $done->send; }, on_failure => failure_cb($done), @@ -496,3 +494,5 @@ sub send_large_size_message { return; } +done_testing; + From 7df4da1e0a3335c797e91c5cf05933d3b8599fb0 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 12:03:30 +0000 Subject: [PATCH 033/165] Version 1.09 --- Changes | 7 ++++++- lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 36ea47b..23a7c98 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,10 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.09 Mon Feb 25 12:03:00 2013 + - Support AMQP heartbeat. + + - Support AMQP 0.9 standard. (Chip Salzenberg) + - Stop defining a _return_cb value when not using the mandatory or immediate flags when publishing a message. This means that if you're not using these flags, but are using an infinite set @@ -8,7 +13,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ routing keys, we still have a problem as we leak callbacks. RT#79511 -1.08 Mon Aug 27 08:43:00 +1.08 Mon Aug 27 08:43:00 2012 - Improve Data::Dumper options for protocol dumps (Chip Salzenberg) - More thoroughly eliminate memory leaks on incoming messages (Chip Salzenberg) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 1fab2a9..101cc06 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -30,7 +30,7 @@ use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.08'; +our $VERSION = '1.09'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 17c1773..7ab0e26 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -8,7 +8,7 @@ use Scalar::Util qw(weaken); use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } -our $VERSION = '1.08'; +our $VERSION = '1.09'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index e804d65..eac0247 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.08'; +our $VERSION = '1.09'; sub new { my $class = shift; From 4116b9f3693ef74474daf40934bd68c0e1945779 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 12:54:45 +0000 Subject: [PATCH 034/165] Add TLS connection support. RT#81729 --- Changes | 2 ++ lib/AnyEvent/RabbitMQ.pm | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 23a7c98..1cb5172 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Add TLS connection support. RT#81729 + 1.09 Mon Feb 25 12:03:00 2013 - Support AMQP heartbeat. diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 101cc06..01e9783 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -117,7 +117,7 @@ sub connect { sprintf('Error connecting to AMQP Server %s:%s: %s', $args{host}, $args{port}, $!) ); - $self->{_handle} = AnyEvent::Handle->new( + my %handle_args = ( fh => $fh, on_error => sub { my ($handle, $fatal, $message) = @_; @@ -136,6 +136,10 @@ sub connect { if exists $self->{drain_condvar}; }, ); + if ($args{tls}) { + $handle_args{tls} = 'connect'; + } + $self->{_handle} = AnyEvent::Handle->new(%handle_args); $self->_read_loop($args{on_close}, $args{on_read_failure}); $self->_start(%args,); }, @@ -574,6 +578,7 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. pass => 'guest', vhost => '/', timeout => 1, + tls => 0, # Or 1 if you'd like SSL on_success => sub { $ar->open_channel( on_success => sub { From dd25a0b3a300f35fe2f025775b406f562ff49833 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 13:03:04 +0000 Subject: [PATCH 035/165] Clarify relationship to Net::RabbitFoot. RT#71099 --- Changes | 2 ++ lib/AnyEvent/RabbitMQ.pm | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Changes b/Changes index 1cb5172..53af5e9 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Clarify relationship to Net::RabbitFoot. RT#71099 + - Add TLS connection support. RT#81729 1.09 Mon Feb 25 12:03:00 2013 diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 01e9783..d592dbe 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -626,6 +626,8 @@ You can use AnyEvent::RabbitMQ to - AnyEvent::RabbitMQ is known to work with RabbitMQ versions 2.5.1 and versions 0-8 and 0-9-1 of the AMQP specification. +This client is the non-blocking version, for a blocking version with a similar API, see L. + =head1 AUTHOR Masahito Ikuta Ecooldaemon@gmail.comE From dc90d1013ecbe581c6426117648fae3531dbe842 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 13:49:01 +0000 Subject: [PATCH 036/165] Fix Pod spelling --- xt/01_podspell.t | 1 + 1 file changed, 1 insertion(+) diff --git a/xt/01_podspell.t b/xt/01_podspell.t index cac0fc4..5d50bdd 100644 --- a/xt/01_podspell.t +++ b/xt/01_podspell.t @@ -6,6 +6,7 @@ set_spell_cmd('aspell list'); $ENV{LANG} = 'C'; all_pod_files_spelling_ok('lib'); __DATA__ +API signalled cancelled Masahito Ikuta From c0ec26b7007edb688afbda96a1b893fdb0615176 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 25 Feb 2013 13:48:27 +0000 Subject: [PATCH 037/165] Version 1.10 --- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 53af5e9..3c98030 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.10 Mon Feb 25 13:48:00 2013 - Clarify relationship to Net::RabbitFoot. RT#71099 - Add TLS connection support. RT#81729 diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index d592dbe..eacc7a4 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -30,7 +30,7 @@ use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.09'; +our $VERSION = '1.10'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 7ab0e26..a1b114e 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -8,7 +8,7 @@ use Scalar::Util qw(weaken); use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } -our $VERSION = '1.09'; +our $VERSION = '1.10'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index eac0247..4c4edcf 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.09'; +our $VERSION = '1.10'; sub new { my $class = shift; From cc0dbc39434bcbedf239654a7ca0d1dee1d12180 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 5 Mar 2013 14:49:10 +0000 Subject: [PATCH 038/165] Ensure the on_success handler gets called on close if there are no channels. --- lib/AnyEvent/RabbitMQ.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index eacc7a4..18cd054 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -350,6 +350,11 @@ sub close { } ); }; + + if (scalar(keys %{ $self->{_channels} })==0) { + $args{on_success}->(@_); + } + for my $id (keys %{$self->{_channels}}) { my $channel = $self->{_channels}->{$id} or next; # Could have already gone away on global destruction.. From ad8f04b1020853a69359d6d39309ff6bf99ccd13 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 5 Mar 2013 19:10:42 +0000 Subject: [PATCH 039/165] Changelog --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 3c98030..fea6d50 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Fix on_success callback for the Channel->close method (davel). + 1.10 Mon Feb 25 13:48:00 2013 - Clarify relationship to Net::RabbitFoot. RT#71099 From ab4eadc37810c2ba4228636eb19410207b26b2e0 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Tue, 5 Mar 2013 22:23:01 +0000 Subject: [PATCH 040/165] Version 1.11 --- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index fea6d50..28e14ae 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.11 Tue Mar 5 22:22:00 2013 - Fix on_success callback for the Channel->close method (davel). 1.10 Mon Feb 25 13:48:00 2013 diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 18cd054..62ef7c5 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -30,7 +30,7 @@ use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.10'; +our $VERSION = '1.11'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index a1b114e..9a11db5 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -8,7 +8,7 @@ use Scalar::Util qw(weaken); use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } -our $VERSION = '1.10'; +our $VERSION = '1.11'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 4c4edcf..054ee05 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.10'; +our $VERSION = '1.11'; sub new { my $class = shift; From af7c3138d9a5a8e9f7a110614ef7e83f5509d260 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Fri, 8 Mar 2013 14:12:13 +0000 Subject: [PATCH 041/165] Spontaneously emit hearts as per amqp 0.9.1 spec. The AMQP spec says, "The client should start sending heartbeats after receiving a Connection.Tune method, and start monitoring heartbeats after receiving Connection.Open." There is no mention of merely responding to heartbeat packets emitted by the server. --- lib/AnyEvent/RabbitMQ.pm | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 62ef7c5..6bc83a0 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -186,10 +186,13 @@ sub _read_loop { '-----------', "\n"; } + # TODO - check that a packet has been received within two times the + # heartbeat period. + my $id = $frame->channel; if (0 == $id) { if ($frame->type_id == 8) { - $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); + # Heartbeat, no action needs taking. return; } return if !$self->_check_close_and_clean($frame, $close_cb,); @@ -220,6 +223,7 @@ sub _check_close_and_clean { my $method_frame = $frame->method_frame; return 1 if !$method_frame->isa('Net::AMQP::Protocol::Connection::Close'); + delete $self->{_heartbeat}; $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()); $self->{_channels} = {}; $self->{_is_open} = 0; @@ -296,6 +300,17 @@ sub _tune { ); $self->_open(%args,); + + if ($frame->method_frame->heartbeat > 0) { + $self->{_heartbeat} = AnyEvent->timer( + after => $frame->method_frame->heartbeat, + interval => $frame->method_frame->heartbeat, + cb => sub { + $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); + }, + ); + } + }, $args{on_failure}, ); From ae00a80c6433ed1b077b4c67f9d8094c90003360 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 27 Mar 2013 10:40:44 +0000 Subject: [PATCH 042/165] Fix RT#84222, continue reading AMQP packets after a heartbeat. --- lib/AnyEvent/RabbitMQ.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 6bc83a0..49596ba 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -193,7 +193,7 @@ sub _read_loop { if (0 == $id) { if ($frame->type_id == 8) { # Heartbeat, no action needs taking. - return; + goto &_read_loop; } return if !$self->_check_close_and_clean($frame, $close_cb,); $self->{_queue}->push($frame); From 9d049c55d1f85a8d72c874e90f56eac1d9544152 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 27 Mar 2013 17:49:59 +0000 Subject: [PATCH 043/165] Don't call goto without settin @_. --- lib/AnyEvent/RabbitMQ.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 49596ba..e6e397a 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -193,10 +193,11 @@ sub _read_loop { if (0 == $id) { if ($frame->type_id == 8) { # Heartbeat, no action needs taking. - goto &_read_loop; } - return if !$self->_check_close_and_clean($frame, $close_cb,); - $self->{_queue}->push($frame); + else { + return if !$self->_check_close_and_clean($frame, $close_cb,); + $self->{_queue}->push($frame); + } } else { my $channel = $self->{_channels}->{$id}; if (defined $channel) { From a0437534e488d4fc323c80a1867aab7f7b4c2354 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 11 Apr 2013 20:30:46 +0100 Subject: [PATCH 044/165] Changelog --- Changes | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Changes b/Changes index 28e14ae..13de614 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,14 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Fix RT#84222, continue reading AMQP packets after a heartbeat. + + - Spontaneously emit hearts as per amqp 0.9.1 spec. + + The AMQP spec says, "The client should start sending heartbeats after + receiving a Connection.Tune method, and start monitoring heartbeats after + receiving Connection.Open." There is no mention of merely responding to + heartbeat packets emitted by the server. (Dave Lambley) + 1.11 Tue Mar 5 22:22:00 2013 - Fix on_success callback for the Channel->close method (davel). From 05a46f0f13d1cfa65b51e6f8e31fbdc4786d9a78 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Mon, 25 Mar 2013 23:27:55 -0700 Subject: [PATCH 045/165] allow AMQP client to adjust tuning, e.g. heartbeat --- lib/AnyEvent/RabbitMQ.pm | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index e6e397a..e1c5eed 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -292,20 +292,20 @@ sub _tune { sub { my $frame = shift; + my %tune = map { my $t = $args{tune}{$_}; + ( $_ => defined($t) ? $t : $frame->method_frame->$_ ) } + qw( channel_max frame_max heartbeat ); + $self->_push_write( - Net::AMQP::Protocol::Connection::TuneOk->new( - channel_max => $frame->method_frame->channel_max, - frame_max => $frame->method_frame->frame_max, - heartbeat => $frame->method_frame->heartbeat, - ), + Net::AMQP::Protocol::Connection::TuneOk->new(%tune) ); $self->_open(%args,); - if ($frame->method_frame->heartbeat > 0) { + if ($tune{heartbeat} > 0) { $self->{_heartbeat} = AnyEvent->timer( - after => $frame->method_frame->heartbeat, - interval => $frame->method_frame->heartbeat, + after => $tune{heartbeat}, + interval => $tune{heartbeat}, cb => sub { $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); }, @@ -600,6 +600,7 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. vhost => '/', timeout => 1, tls => 0, # Or 1 if you'd like SSL + tune => { heartbeat => 30, channel_max => $whatever, frame_max = $whatever }, on_success => sub { $ar->open_channel( on_success => sub { From 618d6b8f0b81c5b1e2c424eda2ccc1b3026a3482 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 11 Apr 2013 20:45:04 +0100 Subject: [PATCH 046/165] Changelog --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index 13de614..96e17fb 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Allow AMQP client to adjust tuning, e.g. heartbeat + (Chip Salzenberg) + - Fix RT#84222, continue reading AMQP packets after a heartbeat. - Spontaneously emit hearts as per amqp 0.9.1 spec. From b4482d8bc9c6cb83f70d4d2262ecf09c7f2ed9e2 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Thu, 11 Apr 2013 20:45:43 +0100 Subject: [PATCH 047/165] Version 1.12 --- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 96e17fb..8fd6184 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.12 Thu Apr 11 20:45:00 2013 - Allow AMQP client to adjust tuning, e.g. heartbeat (Chip Salzenberg) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index e1c5eed..6d62b8a 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -30,7 +30,7 @@ use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; -our $VERSION = '1.11'; +our $VERSION = '1.12'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 9a11db5..14d9ebb 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -8,7 +8,7 @@ use Scalar::Util qw(weaken); use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } -our $VERSION = '1.11'; +our $VERSION = '1.12'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 054ee05..d61984f 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.11'; +our $VERSION = '1.12'; sub new { my $class = shift; From ff0a88970b42f86951009b77832c776ea71d0bfd Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 25 Apr 2013 02:32:32 -0700 Subject: [PATCH 048/165] Require Net::AMQP 0.06 to: get consume cancel notifications (e.g. queue deletion); properly encode user-provided header strings that look like numbers. Fix race between server-sent and client-sent cancellation. Expect server to send heartbeats as promised. If it doesn't, go President Madagasgar on its ass and SHUT DOWN EVERYTHING. Rearrangeme many things and weaken many references to eliminate bad circular references. Some circular refs are actually good, though; leave those. Allow customized client_properties on connection. Make test output clearer. --- Changes | 32 ++++-- Makefile.PL | 3 +- lib/AnyEvent/RabbitMQ.pm | 191 ++++++++++++++++++------------- lib/AnyEvent/RabbitMQ/Channel.pm | 155 ++++++++++++++++--------- xt/05_multi_channel.t | 24 +++- 5 files changed, 259 insertions(+), 146 deletions(-) diff --git a/Changes b/Changes index 8fd6184..ac19941 100644 --- a/Changes +++ b/Changes @@ -1,17 +1,33 @@ Revision history for Perl extension AnyEvent::RabbitMQ + + - Require Net::AMQP 0.06 to: + + Get consume cancel notifications (e.g. queue deletion) + + Properly encode user-provided header strings that look like numbers + - Fix race between server-sent and client-sent cancellation. + + - Expect server to send heartbeats as promised. If it doesn't, go President + Madagasgar on its ass and SHUT DOWN EVERYTHING. + + - Rearrange many things and weaken many references to eliminate bad circular + references. Some circular refs are actually good, though; leave those. + + - Allow customized client_properties on connection. + + - Make test output clearer. + 1.12 Thu Apr 11 20:45:00 2013 - - Allow AMQP client to adjust tuning, e.g. heartbeat - (Chip Salzenberg) + - Allow AMQP client to adjust tuning, e.g. heartbeat + (Chip Salzenberg) - - Fix RT#84222, continue reading AMQP packets after a heartbeat. + - Fix RT#84222, continue reading AMQP packets after a heartbeat. - - Spontaneously emit hearts as per amqp 0.9.1 spec. + - Spontaneously emit hearts as per amqp 0.9.1 spec. - The AMQP spec says, "The client should start sending heartbeats after - receiving a Connection.Tune method, and start monitoring heartbeats after - receiving Connection.Open." There is no mention of merely responding to - heartbeat packets emitted by the server. (Dave Lambley) + The AMQP spec says, "The client should start sending heartbeats after + receiving a Connection.Tune method, and start monitoring heartbeats after + receiving Connection.Open." There is no mention of merely responding to + heartbeat packets emitted by the server. (Dave Lambley) 1.11 Tue Mar 5 22:22:00 2013 - Fix on_success callback for the Channel->close method (davel). diff --git a/Makefile.PL b/Makefile.PL index f731120..c60be3f 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -4,7 +4,7 @@ name 'AnyEvent-RabbitMQ'; all_from 'lib/AnyEvent/RabbitMQ.pm'; requires 'List::MoreUtils'; -requires 'Net::AMQP'; +requires 'Net::AMQP' => '0.06'; requires 'AnyEvent'; requires 'Devel::GlobalDestruction'; requires 'namespace::clean'; @@ -15,6 +15,7 @@ tests 't/*.t'; author_tests 'xt'; install_share; +perl_version '5.006'; build_requires 'Test::More'; build_requires 'Test::Exception'; build_requires 'version'; diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 6d62b8a..83609dd 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -3,12 +3,12 @@ package AnyEvent::RabbitMQ; use strict; use warnings; use Carp qw(confess croak); +use Scalar::Util qw(refaddr); use List::MoreUtils qw(none); use Devel::GlobalDestruction; use File::ShareDir; use Readonly; use Scalar::Util qw/ weaken /; -use namespace::clean; require Data::Dumper; sub Dumper { @@ -24,12 +24,14 @@ sub Dumper { use AnyEvent::Handle; use AnyEvent::Socket; -use Net::AMQP; +use Net::AMQP 0.06; use Net::AMQP::Common qw(:all); use AnyEvent::RabbitMQ::Channel; use AnyEvent::RabbitMQ::LocalQueue; +use namespace::clean; + our $VERSION = '1.12'; Readonly my $DEFAULT_AMQP_SPEC @@ -63,10 +65,15 @@ sub channels { return $self->{_channels}; } -sub delete_channel { +sub _delete_channel { my $self = shift; - my ($id) = @_; - return defined delete $self->{_channels}->{$id}; + my ($channel,) = @_; + my $c = $self->{_channels}->{$channel->id}; + if (defined($c) && refaddr($c) == refaddr($channel)) { + delete $self->{_channels}->{$channel->id}; + return 1; + } + return 0; } sub login_user { @@ -109,37 +116,40 @@ sub connect { warn 'connect to ', $args{host}, ':', $args{port}, '...', "\n"; } + weaken(my $weak_self = $self); $self->{_connect_guard} = AnyEvent::Socket::tcp_connect( $args{host}, $args{port}, sub { + my $self = $weak_self or return; my $fh = shift or return $args{on_failure}->( sprintf('Error connecting to AMQP Server %s:%s: %s', $args{host}, $args{port}, $!) ); - my %handle_args = ( + my $close_cb = $args{on_close}; + my $failure_cb = $args{on_failure}; + $self->{_handle} = AnyEvent::Handle->new( fh => $fh, on_error => sub { my ($handle, $fatal, $message) = @_; + my $self = $weak_self or return; - $self->{_channels} = {}; - if (!$self->{_is_open}) { - $args{on_failure}->(@_); + if ($self->{_is_open}) { + $self->_force_close($close_cb, $message); + } + else { + $failure_cb->(@_); } - $self->{_is_open} = 0; - $self->_disconnect(); - $args{on_close}->($message); }, on_drain => sub { my ($handle) = @_; + my $self = $weak_self or return; + $self->{drain_condvar}->send if exists $self->{drain_condvar}; }, + $args{tls} ? (tls => 'connect') : (), ); - if ($args{tls}) { - $handle_args{tls} = 'connect'; - } - $self->{_handle} = AnyEvent::Handle->new(%handle_args); $self->_read_loop($args{on_close}, $args{on_read_failure}); $self->_start(%args,); }, @@ -160,7 +170,9 @@ sub _read_loop { return if !defined $self->{_handle}; # called on_error + weaken(my $weak_self = $self); $self->{_handle}->push_read(chunk => 8, sub { + my $self = $weak_self or return; my $data = $_[1]; my $stack = $_[1]; @@ -178,17 +190,17 @@ sub _read_loop { } $self->{_handle}->push_read(chunk => $length, sub { + my $self = $weak_self or return; $stack .= $_[1]; my ($frame) = Net::AMQP->parse_raw_frames(\$stack); + $self->{_heartbeat_recv} = time if $self->{_heartbeat}; + if ($self->{verbose}) { warn '[C] <-- [S] ', Dumper($frame), '-----------', "\n"; } - # TODO - check that a packet has been received within two times the - # heartbeat period. - my $id = $frame->channel; if (0 == $id) { if ($frame->type_id == 8) { @@ -224,12 +236,24 @@ sub _check_close_and_clean { my $method_frame = $frame->method_frame; return 1 if !$method_frame->isa('Net::AMQP::Protocol::Connection::Close'); - delete $self->{_heartbeat}; + delete $self->{_heartbeat_timer}; $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()); + $self->_force_close($close_cb, $frame); + return; +} + +sub _force_close { + my $self = shift; + my ($close_cb, $why,) = @_; + + for my $channel (values %{ $self->{_channels} }) { + $channel->_closed(ref($why) ? $why : $channel->_close_frame($why)); + } $self->{_channels} = {}; $self->{_is_open} = 0; - $self->_disconnect(); - $close_cb->($frame); + $self->{_handle}->push_shutdown; + + $close_cb->($why); return; } @@ -261,10 +285,15 @@ sub _start { $self->_push_write( Net::AMQP::Protocol::Connection::StartOk->new( client_properties => { - platform => 'Perl', - product => __PACKAGE__, - information => 'http://d.hatena.ne.jp/cooldaemon/', - version => __PACKAGE__->VERSION, + platform => 'Perl', + product => __PACKAGE__, + information => 'http://d.hatena.ne.jp/cooldaemon/', + version => Net::AMQP::Value::String->new(__PACKAGE__->VERSION), + capabilities => { + consumer_cancel_notify => Net::AMQP::Value::true, + exchange_exchange_bindings => Net::AMQP::Value::true, + }, + %{ $args{client_properties} || {} }, }, mechanism => 'AMQPLAIN', response => { @@ -287,9 +316,11 @@ sub _tune { my $self = shift; my %args = @_; + weaken(my $weak_self = $self); $self->_push_read_and_valid( 'Connection::Tune', sub { + my $self = $weak_self or return; my $frame = shift; my %tune = map { my $t = $args{tune}{$_}; @@ -303,10 +334,26 @@ sub _tune { $self->_open(%args,); if ($tune{heartbeat} > 0) { - $self->{_heartbeat} = AnyEvent->timer( + my $close_cb = $args{on_close}; + my $failure_cb = $args{on_read_failure}; + my $last_recv = 0; + my $idle_cycles = 0; + $self->{_heartbeat_recv} = time; + $self->{_heartbeat_timer} = AnyEvent->timer( after => $tune{heartbeat}, interval => $tune{heartbeat}, cb => sub { + my $self = $weak_self or return; + if ($self->{_heartbeat_recv} != $last_recv) { + $last_recv = $self->{_heartbeat_recv}; + $idle_cycles = 0; + } + elsif (++$idle_cycles > 1) { + delete $self->{_heartbeat_timer}; + $failure_cb->("Heartbeat lost"); + $self->_force_close($close_cb, "Heartbeat lost"); + return; + } $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); }, ); @@ -343,8 +390,6 @@ sub _open { sub close { my $self = shift; - my $weak_self = $self; - weaken($weak_self); my %args = $self->_set_cbs(@_); if (!$self->{_is_open}) { @@ -352,74 +397,59 @@ sub close { return $self; } - my $channels_to_close = 0; - my $all_closed_cb = sub { - return unless 0 == $channels_to_close; - $weak_self->_close( - sub { - $weak_self->_disconnect(); - $args{on_success}->(@_); - }, - sub { - $weak_self->_disconnect(); - $args{on_failure}->(@_); - } - ); + my $cv = AE::cv { + $self->_finish_close(%args); }; - if (scalar(keys %{ $self->{_channels} })==0) { - $args{on_success}->(@_); - } + $cv->begin; for my $id (keys %{$self->{_channels}}) { my $channel = $self->{_channels}->{$id} or next; # Could have already gone away on global destruction.. - $channels_to_close++; - + $cv->begin; $channel->close( - on_success => sub { - $channels_to_close--; - $all_closed_cb->(); - }, - on_failure => sub { - $channels_to_close--; - $all_closed_cb->(); - $args{on_failure}->(@_); - }, - ); + on_success => sub { $cv->end }, + on_failure => sub { $cv->end }, + ); } + $cv->end; + return $self; } -sub _close { +sub _finish_close { my $self = shift; - my ($cb, $failure_cb,) = @_; + my %args = @_; if (!$self->{_is_open}) { - $cb->("Already closed"); - return $self; + $args{on_failure}->("Already closed"); + return; } if (my @ch = keys %{$self->{_channels}}) { - $failure_cb->("Can't disconnect with channel(s) open: @ch"); - return $self; + $args{on_failure}->("Can't close connection with channel(s) open: @ch"); + return; } $self->_push_write_and_read( 'Connection::Close', {}, 'Connection::CloseOk', - $cb, $failure_cb, + sub { + # circular ref ok + $self->{_handle}->push_shutdown; + $args{on_success}->(@_); + }, + sub { + # circular ref ok + $self->{_handle}->push_shutdown; + $args{on_failure}->(@_); + }, ); - $self->{_is_open} = 0; - return $self; -} + $self->{_is_open} = 0; -sub _disconnect { - my $self = shift; - $self->{_handle}->push_shutdown; - return $self; + return; } sub open_channel { @@ -461,7 +491,7 @@ sub open_channel { $args{on_success}->($channel); }, on_failure => sub { - $self->delete_channel($id); + $self->_delete_channel($channel); $args{on_failure}->(@_); }, ); @@ -512,8 +542,9 @@ sub _push_read_and_valid { } $failure_cb->( - 'Method is not ' . join(',', @$exp) . "\n" - . 'Method was ' . ref $method_frame + $method_frame->isa('Net::AMQP::Protocol::Channel::Close') + ? 'Channel closed' + : 'Expected ' . join(',', @$exp) . ' but got ' . ref($method_frame) ); }); } @@ -621,14 +652,20 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. ); }, on_failure => $cv, - on_read_failure => sub {die @_}, + on_read_failure => sub { die @_ }, on_return => sub { my $frame = shift; die "Unable to deliver ", Dumper($frame); } on_close => sub { - my $method_frame = shift->method_frame; - die $method_frame->reply_code, $method_frame->reply_text; + my $why = shift; + if (ref($why)) { + my $method_frame = $why->method_frame; + die $method_frame->reply_code, ": ", $method_frame->reply_text; + } + else { + die $why; + } }, ); diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 14d9ebb..244c427 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -4,10 +4,12 @@ use strict; use warnings; use AnyEvent::RabbitMQ::LocalQueue; -use Scalar::Util qw(weaken); +use Scalar::Util qw( looks_like_number weaken ); use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } +use namespace::clean; + our $VERSION = '1.12'; sub new { @@ -31,14 +33,18 @@ sub _reset { _is_confirm => 0, _publish_tag => 0, _publish_cbs => {}, # values: [on_ack, on_nack, on_return] - _consumer_cbs => {}, - _consumer_cans => {}, + _consumer_cbs => {}, # values: [on_consume, on_cancel...] ); @$self{keys %a} = values %a; return $self; } +sub id { + my $self = shift; + return $self->{id}; +} + sub is_open { my $self = shift; return $self->{_is_open}; @@ -96,33 +102,26 @@ sub close { # open, but we've closed it - a more elegant fix would be to mark that # the channel is opening, and wait for it to open before closing it if (!$self->{_is_open}) { - $connection->delete_channel($self->{id}); + $connection->_delete_channel($self); $args{on_success}->($self); return $self; } - # spell it out, so the callbacks always can call ->method_frame - my $close_frame = Net::AMQP::Frame::Method->new( - method_frame => Net::AMQP::Protocol::Channel::Close->new, - ); - $connection->_push_write( - $close_frame, + $self->_close_frame, $self->{id}, ); - weaken(my $wself = $self); - $connection->_push_read_and_valid( 'Channel::CloseOk', sub { - my $me = $wself or return; - $me->_close($close_frame, 0); + # circular ref ok + $self->_closed(); $args{on_success}->(); }, sub { - my $me = $wself or return; - $me->_close($close_frame, 0); + # circular ref ok + $self->_closed(); $args{on_failure}->(); }, $self->{id}, @@ -131,31 +130,49 @@ sub close { return $self; } -sub _close { +sub _closed { my $self = shift; - my ($frame, $forced) = @_; - - my $connection = $self->{connection}; - my $on_close = $self->{on_close}; + my ($frame,) = @_; + $frame ||= $self->_close_frame; + $self->{_is_open} or return; $self->{_is_open} = 0; - if ($frame) { - $self->{_queue}->_flush($frame); - $self->{_content_queue}->_flush($frame); - } + + # Perform callbacks for all outstanding commands + $self->{_queue}->_flush($frame); + $self->{_content_queue}->_flush($frame); + + # Report cancelation of all outstanding consumes + my @tags = keys %{ $self->{_consumer_cbs} }; + $self->_canceled($_, $frame) for @tags; + + # Reset state (redundant?) $self->_reset; - $connection->delete_channel($self->{id}) if $connection; + if (my $connection = $self->{connection}) { + $connection->_delete_channel($self); + } - if (defined $on_close) { + if (my $cb = $self->{on_close}) { local $@; - $on_close->($frame); + eval { $cb->($frame) }; warn "Error in channel on_close callback, ignored:\n $@ " if $@; } return $self; } +sub _close_frame { + my $self = shift; + my ($text,) = @_; + + Net::AMQP::Frame::Method->new( + method_frame => Net::AMQP::Protocol::Channel::Close->new( + reply_text => $text, + ), + ); +} + sub declare_exchange { my $self = shift; my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); @@ -352,9 +369,9 @@ sub publish { $tag = ++$self->{_publish_tag}; if ($return_cb) { $header_args = { %$header_args }; - $header_args->{headers}{_ar_return} = $tag; # just reuse the same value, why not + $header_args->{headers}->{_ar_return} = $tag; # just reuse the same value, why not } - $self->{_publish_cbs}{$tag} = [$ack_cb, $nack_cb, $return_cb]; + $self->{_publish_cbs}->{$tag} = [$ack_cb, $nack_cb, $return_cb]; } $self->_publish( @@ -391,6 +408,16 @@ sub _header { my $weight = delete $args->{weight} || 0; + # user-provided message headers must be strings. protect values that look like numbers. + my $headers = $args->{headers} || {}; + my @prot = grep { my $v = $headers->{$_}; !ref($v) && looks_like_number($v) } keys %$headers; + if (@prot) { + $headers = { + %$headers, + map { $_ => Net::AMQP::Value::String->new($headers->{$_}) } @prot + }; + } + $self->{connection}->_push_write( Net::AMQP::Frame::Header->new( weight => $weight, @@ -398,7 +425,6 @@ sub _header { header_frame => Net::AMQP::Protocol::Basic::ContentHeader->new( content_type => 'application/octet-stream', content_encoding => undef, - headers => {}, delivery_mode => 1, priority => 1, correlation_id => undef, @@ -410,6 +436,7 @@ sub _header { app_id => undef, cluster_id => undef, %$args, + headers => $headers, ), ), $self->{id}, @@ -436,7 +463,8 @@ sub consume { return $self if !$self->_check_open($failure_cb); my $consumer_cb = delete $args{on_consume} || sub {}; - + my $cancel_cb = delete $args{on_cancel} || sub {}; + $self->{connection}->_push_write_and_read( 'Basic::Consume', { @@ -444,16 +472,16 @@ sub consume { no_local => 0, no_ack => 1, exclusive => 0, + %args, # queue ticket => 0, nowait => 0, # FIXME }, - 'Basic::ConsumeOk', + 'Basic::ConsumeOk', sub { my $frame = shift; - $self->{_consumer_cbs}->{ - $frame->method_frame->consumer_tag - } = $consumer_cb; + my $tag = $frame->method_frame->consumer_tag; + $self->{_consumer_cbs}->{$tag} = [ $consumer_cb, $cancel_cb ]; $cb->($frame); }, $failure_cb, @@ -474,12 +502,12 @@ sub cancel { return $self; } - if (!$self->{_consumer_cbs}->{$args{consumer_tag}}) { + my $cons_cbs = $self->{_consumer_cbs}->{$args{consumer_tag}}; + unless ($cons_cbs) { $failure_cb->('Unknown consumer_tag'); return $self; } - - $self->{_consumer_cans}{$args{consumer_tag}} = $cb; + push @$cons_cbs, $cb; $self->{connection}->_push_write( Net::AMQP::Protocol::Basic::Cancel->new( @@ -492,6 +520,20 @@ sub cancel { return $self; } +sub _canceled { + my $self = shift; + my ($tag, $frame,) = @_; + + my $cons_cbs = delete $self->{_consumer_cbs}->{$tag} + or return 0; + + shift @$cons_cbs; # no more deliveries + for my $cb (reverse @$cons_cbs) { + $cb->($frame); + } + return 1; +} + sub get { my $self = shift; my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); @@ -696,20 +738,19 @@ sub push_queue_or_consume { Net::AMQP::Protocol::Channel::CloseOk->new(), $self->{id}, ); - $self->_close($frame, 0); + $self->_closed($frame); return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Deliver')) { - my $cb = $self->{_consumer_cbs}->{ - $method_frame->consumer_tag - } || sub {}; + my $cons_cbs = $self->{_consumer_cbs}->{$method_frame->consumer_tag}; + my $cb = ($cons_cbs && $cons_cbs->[0]) || sub {}; $self->_push_read_header_and_body('deliver', $frame, $cb, $failure_cb); return $self; - } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::CancelOk')) { - my $can_cb = delete $self->{_consumer_cans}{$method_frame->consumer_tag}; - if ($can_cb) { - $can_cb->($method_frame); - } - else { + } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::CancelOk') || + $method_frame->isa('Net::AMQP::Protocol::Basic::Cancel')) { + # CancelOk means we asked for a cancel. + # Cancel means queue was deleted; it is not AMQP, but RMQ supports it. + if (!$self->_canceled($method_frame->consumer_tag, $frame) + && $method_frame->isa('Net::AMQP::Protocol::Basic::CancelOk')) { $failure_cb->("Received CancelOk for unknown consumer tag " . $method_frame->consumer_tag); } return $self; @@ -721,10 +762,10 @@ sub push_queue_or_consume { my $headers = $ret->{header}->headers || {}; my $onret_cb; if (defined(my $tag = $headers->{_ar_return})) { - my $cbs = delete $me->{_publish_cbs}{$tag}; + my $cbs = delete $me->{_publish_cbs}->{$tag}; $onret_cb = $cbs->[2] if $cbs; } - $onret_cb ||= $me->{on_return} || $me->{connection}{on_return} || sub {}; # oh well + $onret_cb ||= $me->{on_return} || $me->{connection}->{on_return} || sub {}; # oh well $onret_cb->($frame); }; $self->_push_read_header_and_body('return', $frame, $cb, $failure_cb); @@ -749,7 +790,7 @@ sub push_queue_or_consume { my $cbi = ($resp eq 'Ack') ? 0 : 1; for my $tag (@tags) { my $cbs; - if (not $cbs = delete $self->{_publish_cbs}{$tag}) { + if (not $cbs = delete $self->{_publish_cbs}->{$tag}) { $failure_cb->("Received $resp of unknown delivery tag $tag"); } elsif ($cbs->[$cbi]) { @@ -767,7 +808,7 @@ sub push_queue_or_consume { $self->{id}, ); my $cbname = $self->{_is_active} ? 'on_active' : 'on_inactive'; - my $cb = $self->{$cbname} || $self->{connection}{$cbname} || sub {}; + my $cb = $self->{$cbname} || $self->{connection}->{$cbname} || sub {}; $cb->($frame); return $self; } @@ -871,8 +912,6 @@ sub DESTROY { return; } -1; - 1; __END__ @@ -1022,6 +1061,12 @@ Arguments: Callback called with an argument of the message which has been consumed. +=item on_cancel + +Callback called if consumption is canceled. This may be at client request +or as a side effect of queue deletion. (Notification of queue deletion is a +RabbitMQ extension.) + =item consumer_tag Identifies this consumer, will be auto-generated if you do not provide it, but you must diff --git a/xt/05_multi_channel.t b/xt/05_multi_channel.t index 2c37b11..d814013 100644 --- a/xt/05_multi_channel.t +++ b/xt/05_multi_channel.t @@ -24,7 +24,7 @@ eval { plan skip_all => 'Connection failure: ' . $conf{host} . ':' . $conf{port} if $@; -plan tests => 1; +plan tests => 3; use AnyEvent::RabbitMQ; @@ -36,14 +36,19 @@ my @queues = map { declare_queue($ch, $queue,); my $done = AnyEvent->condvar; + my $cdone = AnyEvent->condvar; consume($ch, $queue, sub { my $response = shift; return if 'stop' ne $response->{body}->payload; $done->send(); + }, sub { + $cdone->send(); }); - {name => $queue, cv => $done}; + {name => $queue, cv => $done, ccv => $cdone}; } (1..5); +pass('queue setup'); + my $ch = open_channel($ar); for my $queue (@queues) { publish($ch, $queue->{name}, 'hello'); @@ -62,6 +67,14 @@ for my $queue (@queues) { delete_queue($ch, $queue->{name}); } +my $ccount = 0; +for my $queue (@queues) { + $queue->{ccv}->recv; + $ccount++; +} + +is($ccount, 5, 'cancel count'); + close_ar($ar); sub connect_ar { @@ -70,7 +83,7 @@ sub connect_ar { (map {$_ => $conf{$_}} qw(host port user pass vhost)), timeout => 1, on_success => sub {$done->send(1)}, - on_failure => sub {$done->send()}, + on_failure => sub { diag @_; $done->send()}, on_close => \&handle_close, ); die 'Connection failure' if !$done->recv; @@ -83,7 +96,7 @@ sub close_ar { my $done = AnyEvent->condvar; $ar->close( on_success => sub {$done->send(1)}, - on_failure => sub {$done->send()}, + on_failure => sub { diag @_; $done->send()}, ); die 'Close failure' if !$done->recv; @@ -135,7 +148,7 @@ sub delete_queue { } sub consume { - my ($ch, $queue, $handle_consume,) = @_; + my ($ch, $queue, $handle_consume, $handle_cancel,) = @_; my $done = AnyEvent->condvar; $ch->consume( @@ -143,6 +156,7 @@ sub consume { on_success => sub {$done->send(1)}, on_failure => sub {$done->send()}, on_consume => $handle_consume, + on_cancel => $handle_cancel, ); die 'Consume failure' if !$done->recv; From 3a3e47229c261e5f3365b07cb207750b8667b474 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 2 May 2013 16:49:20 -0700 Subject: [PATCH 049/165] v1.13 --- Changes | 2 +- lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index ac19941..c498923 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ - +1.13 Thu May 2 16:48:58 PDT 2013 - Require Net::AMQP 0.06 to: + Get consume cancel notifications (e.g. queue deletion) + Properly encode user-provided header strings that look like numbers diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 83609dd..94b6552 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.12'; +our $VERSION = '1.13'; Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 244c427..6dd4559 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -10,7 +10,7 @@ BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } use namespace::clean; -our $VERSION = '1.12'; +our $VERSION = '1.13'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index d61984f..9a9469e 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.12'; +our $VERSION = '1.13'; sub new { my $class = shift; From e314282c62329913507ce1bc7bc0d6b1f5a902a4 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Fri, 17 May 2013 18:45:14 -0700 Subject: [PATCH 050/165] Fix paper-bag bug in heartbeat - always lost heartbeat even on active connections --- Changes | 4 ++++ lib/AnyEvent/RabbitMQ.pm | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index c498923..8d6cef9 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for Perl extension AnyEvent::RabbitMQ + + - Fix paper-bag bug in heartbeat - always lost heartbeat even on + active connections + 1.13 Thu May 2 16:48:58 PDT 2013 - Require Net::AMQP 0.06 to: + Get consume cancel notifications (e.g. queue deletion) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 94b6552..648b1a8 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -194,7 +194,7 @@ sub _read_loop { $stack .= $_[1]; my ($frame) = Net::AMQP->parse_raw_frames(\$stack); - $self->{_heartbeat_recv} = time if $self->{_heartbeat}; + $self->{_heartbeat_recv} = time if $self->{_heartbeat_timer}; if ($self->{verbose}) { warn '[C] <-- [S] ', Dumper($frame), From 873fdb2fafe09f91151ca22ef9d55159253cc953 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Fri, 17 May 2013 19:00:49 -0700 Subject: [PATCH 051/165] on channel close, automatically call on_return callbacks for any publishes that are waiting --- Changes | 2 ++ lib/AnyEvent/RabbitMQ/Channel.pm | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 8d6cef9..a6399a8 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ - Fix paper-bag bug in heartbeat - always lost heartbeat even on active connections + - on channel close, automatically call on_return callbacks for any + publishes that are waiting 1.13 Thu May 2 16:48:58 PDT 2013 - Require Net::AMQP 0.06 to: diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 6dd4559..88ecdc9 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -142,11 +142,14 @@ sub _closed { $self->{_queue}->_flush($frame); $self->{_content_queue}->_flush($frame); + # Report rejections of all outstanding publishes + $_->($frame) for grep { defined } map { $_->[2] } values %{ $self->{_publish_cbs} }; + # Report cancelation of all outstanding consumes my @tags = keys %{ $self->{_consumer_cbs} }; $self->_canceled($_, $frame) for @tags; - # Reset state (redundant?) + # Reset state (partly redundant) $self->_reset; if (my $connection = $self->{connection}) { From bae05e88b28137c8ab6db685f3022161fb490ca0 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Fri, 17 May 2013 19:01:17 -0700 Subject: [PATCH 052/165] pass frame to publish on_ack and on_nack callbacks, because why not --- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 88ecdc9..fbf8164 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -797,7 +797,7 @@ sub push_queue_or_consume { $failure_cb->("Received $resp of unknown delivery tag $tag"); } elsif ($cbs->[$cbi]) { - $cbs->[$cbi]->(); + $cbs->[$cbi]->($frame); } } } From 81298353c6de1ee119cb0b21b403306ed0348c47 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Wed, 22 May 2013 00:51:49 -0700 Subject: [PATCH 053/165] on_return is called in *addition* to on_ack/on_nack --- lib/AnyEvent/RabbitMQ/Channel.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index fbf8164..9936dfa 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -142,8 +142,8 @@ sub _closed { $self->{_queue}->_flush($frame); $self->{_content_queue}->_flush($frame); - # Report rejections of all outstanding publishes - $_->($frame) for grep { defined } map { $_->[2] } values %{ $self->{_publish_cbs} }; + # Fake nacks of all outstanding publishes + $_->($frame) for grep { defined } map { $_->[1] } values %{ $self->{_publish_cbs} }; # Report cancelation of all outstanding consumes my @tags = keys %{ $self->{_consumer_cbs} }; @@ -765,7 +765,7 @@ sub push_queue_or_consume { my $headers = $ret->{header}->headers || {}; my $onret_cb; if (defined(my $tag = $headers->{_ar_return})) { - my $cbs = delete $me->{_publish_cbs}->{$tag}; + my $cbs = $me->{_publish_cbs}->{$tag}; $onret_cb = $cbs->[2] if $cbs; } $onret_cb ||= $me->{on_return} || $me->{connection}->{on_return} || sub {}; # oh well From cafa70f75f6a125f68e85300fe7890d72aaef037 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Wed, 22 May 2013 00:52:02 -0700 Subject: [PATCH 054/165] document ->publish --- lib/AnyEvent/RabbitMQ/Channel.pm | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 9936dfa..8c6da77 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -1085,6 +1085,58 @@ Callback called if the subscription fails for any reason. =back +=head2 publish + +Publish a message to an exchange. + +Arguments: + +=over + +=item header + +Hash of AMQP message header info, including the confusingly similar element "headers", +which may contain arbitrary string key/value pairs. + +=item body + +Message body. + +=item mandatory + +Boolean; if true, then if the message doesn't land in a queue (e.g. the exchange has no +bindings), it will be "returned." (see "on_return") + +=item immediate + +Boolean; if true, then if the message cannot be delivered directly to a consumer, it +will be "returned." (see "on_return") + +=item on_ack + +Callback called with the frame that acknowledges receipt (if channel is in confirm mode), +typically L. + +=item on_nack + +Callback called with the frame that declines receipt (if the channel is in confirm mode), +typically L or L. + +=item on_return + +In AMQP, a "returned" message is one that cannot be delivered in compliance with the +C or C flags. + +If in confirm mode, this callback will be called with the frame that reports message +return, typically L. If confirm mode is off or +this callback is not provided, then the channel or connection objects' on_return +callbacks (if any), will be called instead. + +NOTE: If confirm mode is on, the on_ack or on_nack callback will be called whether or +not on_return is called first. + +=back + =head2 cancel Cancel a queue subscription. From 62cc38c9c1509caf79ae15ec3d0aaaec6309c668 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Sun, 2 Jun 2013 23:06:18 -0500 Subject: [PATCH 055/165] update docs for remaining branch changes --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index a6399a8..7f8d5b3 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,9 @@ Revision history for Perl extension AnyEvent::RabbitMQ active connections - on channel close, automatically call on_return callbacks for any publishes that are waiting + - cope with AMQP quirk that in confirm mode, returned messages are + *also* acked/nacked + - document $channel->publish 1.13 Thu May 2 16:48:58 PDT 2013 - Require Net::AMQP 0.06 to: From e89bfaf51f42529bd311d414b905a7a39a8e58b5 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Mon, 3 Jun 2013 22:53:07 -0700 Subject: [PATCH 056/165] don't immediately recycle channel ids; this helps avoid protocol misunderstandings --- lib/AnyEvent/RabbitMQ.pm | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 648b1a8..04cc29e 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -44,6 +44,7 @@ sub new { @_, _is_open => 0, _queue => AnyEvent::RabbitMQ::LocalQueue->new, + _last_chan_id => 0, _channels => {}, _login_user => '', _server_properties => {}, @@ -428,7 +429,7 @@ sub _finish_close { return; } - if (my @ch = keys %{$self->{_channels}}) { + if (my @ch = grep { my $ch = $self->{_channels}{$_}; defined($ch) && $ch->is_open } keys %{$self->{_channels}}) { $args{on_failure}->("Can't close connection with channel(s) open: @ch"); return; } @@ -452,6 +453,8 @@ sub _finish_close { return; } +use constant _MAX_CHANID => 0xFFFF; + sub open_channel { my $self = shift; my %args = $self->_set_cbs(@_); @@ -467,15 +470,19 @@ sub open_channel { } if (!$id) { - for my $candidate_id (1 .. (2**16 - 1)) { - next if defined $self->{_channels}->{$candidate_id}; - $id = $candidate_id; - last; + my $try_id = $self->{_last_chan_id}; + for (1 .. 2**16) { + $try_id = 1 if ++$try_id > _MAX_CHANID; + unless (defined $self->{_channels}->{$try_id}) { + $id = $try_id; + last; + } } if (!$id) { $args{on_failure}->('Ran out of channel ids'); return $self; } + $self->{_last_chan_id} = $id; } my $channel = AnyEvent::RabbitMQ::Channel->new( From 5a49919207f96b3d02cd4ce3569ccaf435c6655f Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Mon, 3 Jun 2013 22:53:36 -0700 Subject: [PATCH 057/165] harden channel close against races; obey spec: ignore all incoming packets after sending Close --- lib/AnyEvent/RabbitMQ/Channel.pm | 132 +++++++++++++++++++------------ 1 file changed, 83 insertions(+), 49 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 8c6da77..4485225 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -4,18 +4,26 @@ use strict; use warnings; use AnyEvent::RabbitMQ::LocalQueue; +use AnyEvent; use Scalar::Util qw( looks_like_number weaken ); use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } use namespace::clean; +use constant { + _ST_CLOSED => 0, + _ST_OPENING => 1, + _ST_OPEN => 2, +}; + our $VERSION = '1.13'; sub new { my $class = shift; my $self = bless { + on_close => sub {}, @_, # id, connection, on_return, on_close, on_inactive, on_active _queue => AnyEvent::RabbitMQ::LocalQueue->new, _content_queue => AnyEvent::RabbitMQ::LocalQueue->new, @@ -28,7 +36,7 @@ sub _reset { my $self = shift; my %a = ( - _is_open => 0, + _state => _ST_CLOSED, _is_active => 0, _is_confirm => 0, _publish_tag => 0, @@ -47,7 +55,7 @@ sub id { sub is_open { my $self = shift; - return $self->{_is_open}; + return $self->{_state} == _ST_OPEN; } sub is_active { @@ -69,20 +77,23 @@ sub open { my $self = shift; my %args = @_; - if ($self->{_is_open}) { + if ($self->{_state} != _ST_CLOSED) { $args{on_failure}->('Channel has already been opened'); return $self; } + $self->{_state} = _ST_OPENING; + $self->{connection}->_push_write_and_read( 'Channel::Open', {}, 'Channel::OpenOk', sub { - $self->{_is_open} = 1; + $self->{_state} = _ST_OPEN; $self->{_is_active} = 1; - $args{on_success}->(); + $args{on_success}->($self); }, sub { - $args{on_failure}->(@_); + $self->{_state} = _ST_CLOSED; + $args{on_failure}->($self); }, $self->{id}, ); @@ -96,36 +107,45 @@ sub close { or return; my %args = $connection->_set_cbs(@_); - # Ensure to remove this channel from the connection even if we're not - # fully open to ensure $rf->close works always. - # FIXME - We can end up racing here so the server thinks the channel is - # open, but we've closed it - a more elegant fix would be to mark that - # the channel is opening, and wait for it to open before closing it - if (!$self->{_is_open}) { - $connection->_delete_channel($self); - $args{on_success}->($self); - return $self; - } + # If open in in progess, wait for it; 1s arbitrary timing. - $connection->_push_write( - $self->_close_frame, - $self->{id}, - ); + weaken(my $wself = $self); + my $t; $t = AE::timer 0, 1, sub { + (my $self = $wself) or undef $t, return; + return if $self->{_state} == _ST_OPENING; - $connection->_push_read_and_valid( - 'Channel::CloseOk', - sub { - # circular ref ok - $self->_closed(); - $args{on_success}->(); - }, - sub { - # circular ref ok - $self->_closed(); - $args{on_failure}->(); - }, - $self->{id}, - ); + # No more tests are required + undef $t; + + # Double close is OK + if ($self->{_state} == _ST_CLOSED) { + $args{on_success}->($self); + return; + } + + $connection->_push_write( + $self->_close_frame, + $self->{id}, + ); + + # The spec says that after a party sends Channel::Close, it MUST + # discard all frames for that channel. So this channel is dead + # immediately. + $self->_closed(); + + $connection->_push_read_and_valid( + 'Channel::CloseOk', + sub { + $args{on_success}->($self); + $self->_orphan(); + }, + sub { + $args{on_failure}->(@_); + $self->_orphan(); + }, + $self->{id}, + ); + }; return $self; } @@ -133,10 +153,10 @@ sub close { sub _closed { my $self = shift; my ($frame,) = @_; - $frame ||= $self->_close_frame; + $frame ||= $self->_close_frame(); - $self->{_is_open} or return; - $self->{_is_open} = 0; + return if $self->{_state} == _ST_CLOSED; + $self->{_state} = _ST_CLOSED; # Perform callbacks for all outstanding commands $self->{_queue}->_flush($frame); @@ -149,19 +169,14 @@ sub _closed { my @tags = keys %{ $self->{_consumer_cbs} }; $self->_canceled($_, $frame) for @tags; + # Report close to on_close callback + { local $@; + eval { $self->{on_close}->($frame) }; + warn "Error in channel on_close callback, ignored:\n $@ " if $@; } + # Reset state (partly redundant) $self->_reset; - if (my $connection = $self->{connection}) { - $connection->_delete_channel($self); - } - - if (my $cb = $self->{on_close}) { - local $@; - eval { $cb->($frame) }; - warn "Error in channel on_close callback, ignored:\n $@ " if $@; - } - return $self; } @@ -176,6 +191,15 @@ sub _close_frame { ); } +sub _orphan { + my $self = shift; + + if (my $connection = $self->{connection}) { + $connection->_delete_channel($self); + } + return $self; +} + sub declare_exchange { my $self = shift; my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); @@ -734,6 +758,9 @@ sub push_queue_or_consume { my $self = shift; my ($frame, $failure_cb,) = @_; + # Note: the spec says that after a party sends Channel::Close, it MUST + # discard all frames for that channel other than Close and CloseOk. + if ($frame->isa('Net::AMQP::Frame::Method')) { my $method_frame = $frame->method_frame; if ($method_frame->isa('Net::AMQP::Protocol::Channel::Close')) { @@ -742,6 +769,13 @@ sub push_queue_or_consume { $self->{id}, ); $self->_closed($frame); + $self->_orphan(); + return $self; + } elsif ($self->{_state} != _ST_OPEN) { + if ($method_frame->isa('Net::AMQP::Protocol::Channel::OpenOk') || + $method_frame->isa('Net::AMQP::Protocol::Channel::CloseOk')) { + $self->{_queue}->push($frame); + } return $self; } elsif ($method_frame->isa('Net::AMQP::Protocol::Basic::Deliver')) { my $cons_cbs = $self->{_consumer_cbs}->{$method_frame->consumer_tag}; @@ -890,7 +924,7 @@ sub _check_open { my $self = shift; my ($failure_cb) = @_; - return 1 if $self->{_is_open}; + return 1 if $self->is_open; $failure_cb->('Channel has already been closed'); return 0; @@ -911,7 +945,7 @@ sub _check_version { sub DESTROY { my $self = shift; - $self->close() if $self->{_is_open}; + $self->close() if $self->is_open; return; } From 54df40dc55d757bc8aac727d4bfddeca7242b4de Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 6 Jun 2013 15:30:28 -0700 Subject: [PATCH 058/165] use Devel::GlobalDestruction to protect Channel --- lib/AnyEvent/RabbitMQ/Channel.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 4485225..48af542 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -6,6 +6,7 @@ use warnings; use AnyEvent::RabbitMQ::LocalQueue; use AnyEvent; use Scalar::Util qw( looks_like_number weaken ); +use Devel::GlobalDestruction; use Carp qw(croak); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } @@ -924,7 +925,7 @@ sub _check_open { my $self = shift; my ($failure_cb) = @_; - return 1 if $self->is_open; + return 1 if $self->is_open(); $failure_cb->('Channel has already been closed'); return 0; @@ -945,7 +946,7 @@ sub _check_version { sub DESTROY { my $self = shift; - $self->close() if $self->is_open; + $self->close() if !in_global_destruction && $self->is_open(); return; } From 8d6842a03b90850829ce888a13a61c5fb490623a Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 6 Jun 2013 15:32:17 -0700 Subject: [PATCH 059/165] use Devel::GlobalDestruction to protect connection --- lib/AnyEvent/RabbitMQ.pm | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 04cc29e..5a2759a 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -606,14 +606,9 @@ sub drain_writes { delete $self->{drain_timer}; } -my $is_gd; - -END { $is_gd++ }; - sub DESTROY { my $self = shift; - return if $is_gd; - $self->close() if defined $self; + $self->close() unless in_global_destruction; return; } From 54ed4a3ad80f492ba14fc00c5450edafc679b26e Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 6 Jun 2013 15:32:48 -0700 Subject: [PATCH 060/165] implement full state machine around connection closing --- lib/AnyEvent/RabbitMQ.pm | 162 +++++++++++++++++++++++---------------- 1 file changed, 96 insertions(+), 66 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 5a2759a..dd2a5c6 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -34,6 +34,13 @@ use namespace::clean; our $VERSION = '1.13'; +use constant { + _ST_CLOSED => 0, + _ST_OPENING => 1, + _ST_OPEN => 2, + _ST_CLOSING => 3, +}; + Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; @@ -42,7 +49,7 @@ sub new { return bless { verbose => 0, @_, - _is_open => 0, + _state => _ST_CLOSED, _queue => AnyEvent::RabbitMQ::LocalQueue->new, _last_chan_id => 0, _channels => {}, @@ -58,7 +65,7 @@ sub verbose { sub is_open { my $self = shift; - $self->{_is_open} + $self->{_state} == _ST_OPEN } sub channels { @@ -100,7 +107,7 @@ sub connect { my $self = shift; my %args = $self->_set_cbs(@_); - if ($self->{_is_open}) { + if ($self->{_state} != _ST_CLOSED) { $args{on_failure}->('Connection has already been opened'); return $self; } @@ -110,19 +117,23 @@ sub connect { $args{timeout} ||= 0; for (qw/ host port /) { - confess("No $_ passed to connect to") unless $args{$_}; + $args{$_} or return $args{on_failure}->("No $_ passed to connect"); } if ($self->{verbose}) { warn 'connect to ', $args{host}, ':', $args{port}, '...', "\n"; } + $self->{_state} = _ST_OPENING; + weaken(my $weak_self = $self); - $self->{_connect_guard} = AnyEvent::Socket::tcp_connect( + my $conn; $conn = AnyEvent::Socket::tcp_connect( $args{host}, $args{port}, sub { + undef $conn; my $self = $weak_self or return; + my $fh = shift or return $args{on_failure}->( sprintf('Error connecting to AMQP Server %s:%s: %s', $args{host}, $args{port}, $!) ); @@ -135,7 +146,7 @@ sub connect { my ($handle, $fatal, $message) = @_; my $self = $weak_self or return; - if ($self->{_is_open}) { + if ($self->is_open) { $self->_force_close($close_cb, $message); } else { @@ -208,7 +219,7 @@ sub _read_loop { # Heartbeat, no action needs taking. } else { - return if !$self->_check_close_and_clean($frame, $close_cb,); + return unless $self->_check_close_and_clean($frame, $close_cb,); $self->{_queue}->push($frame); } } else { @@ -232,27 +243,33 @@ sub _check_close_and_clean { my $self = shift; my ($frame, $close_cb,) = @_; - return 1 if !$frame->isa('Net::AMQP::Frame::Method'); + my $method_frame = $frame->isa('Net::AMQP::Frame::Method') ? $frame->method_frame : undef; + + if ($self->{_state} == _ST_CLOSED) { + return $method_frame && $method_frame->isa('Net::AMQP::Protocol::Connection::CloseOk'); + } - my $method_frame = $frame->method_frame; - return 1 if !$method_frame->isa('Net::AMQP::Protocol::Connection::Close'); + if ($method_frame && $method_frame->isa('Net::AMQP::Protocol::Connection::Close')) { + delete $self->{_heartbeat_timer}; + $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()); + $self->_server_closed($close_cb, $frame); + return; + } - delete $self->{_heartbeat_timer}; - $self->_push_write(Net::AMQP::Protocol::Connection::CloseOk->new()); - $self->_force_close($close_cb, $frame); - return; + return 1; } -sub _force_close { +sub _server_closed { my $self = shift; my ($close_cb, $why,) = @_; + $self->{_state} = _ST_CLOSING; for my $channel (values %{ $self->{_channels} }) { $channel->_closed(ref($why) ? $why : $channel->_close_frame($why)); } $self->{_channels} = {}; - $self->{_is_open} = 0; $self->{_handle}->push_shutdown; + $self->{_state} = _ST_CLOSED; $close_cb->($why); return; @@ -329,37 +346,14 @@ sub _tune { qw( channel_max frame_max heartbeat ); $self->_push_write( - Net::AMQP::Protocol::Connection::TuneOk->new(%tune) + Net::AMQP::Protocol::Connection::TuneOk->new(%tune,) ); - $self->_open(%args,); - if ($tune{heartbeat} > 0) { - my $close_cb = $args{on_close}; - my $failure_cb = $args{on_read_failure}; - my $last_recv = 0; - my $idle_cycles = 0; - $self->{_heartbeat_recv} = time; - $self->{_heartbeat_timer} = AnyEvent->timer( - after => $tune{heartbeat}, - interval => $tune{heartbeat}, - cb => sub { - my $self = $weak_self or return; - if ($self->{_heartbeat_recv} != $last_recv) { - $last_recv = $self->{_heartbeat_recv}; - $idle_cycles = 0; - } - elsif (++$idle_cycles > 1) { - delete $self->{_heartbeat_timer}; - $failure_cb->("Heartbeat lost"); - $self->_force_close($close_cb, "Heartbeat lost"); - return; - } - $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); - }, - ); + $self->_start_heartbeat($tune{heartbeat}, %args,); } + $self->_open(%args,); }, $args{on_failure}, ); @@ -367,6 +361,39 @@ sub _tune { return $self; } +sub _start_heartbeat { + my ($self, $interval, %args,) = @_; + + my $close_cb = $args{on_close}; + my $failure_cb = $args{on_read_failure}; + my $last_recv = 0; + my $idle_cycles = 0; + weaken(my $weak_self = $self); + my $timer_cb = sub { + my $self = $weak_self or return; + if ($self->{_heartbeat_recv} != $last_recv) { + $last_recv = $self->{_heartbeat_recv}; + $idle_cycles = 0; + } + elsif (++$idle_cycles > 1) { + delete $self->{_heartbeat_timer}; + $failure_cb->("Heartbeat lost"); + $self->_force_close($close_cb, "Heartbeat lost"); + return; + } + $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); + }; + + $self->{_heartbeat_recv} = time; + $self->{_heartbeat_timer} = AnyEvent->timer( + after => $interval, + interval => $interval, + cb => $timer_cb, + ); + + return $self; +} + sub _open { my $self = shift; my %args = @_; @@ -379,7 +406,7 @@ sub _open { }, 'Connection::OpenOk', sub { - $self->{_is_open} = 1; + $self->{_state} = _ST_OPEN; $self->{_login_user} = $args{user}; $args{on_success}->($self); }, @@ -390,32 +417,40 @@ sub _open { } sub close { + return if in_global_destruction; my $self = shift; my %args = $self->_set_cbs(@_); - if (!$self->{_is_open}) { + if (!$self->{_state} == _ST_CLOSED) { $args{on_success}->(@_); return $self; } + if ($self->{_state} != _ST_OPEN) { + $args{on_failure}->(($self->{_state} == _ST_OPENING ? "open" : "close") . " already in progress"); + return $self; + } + $self->{_state} = _ST_CLOSING; my $cv = AE::cv { + delete $self->{_closing}; $self->_finish_close(%args); }; - $cv->begin; - - for my $id (keys %{$self->{_channels}}) { - my $channel = $self->{_channels}->{$id} - or next; # Could have already gone away on global destruction.. - - $cv->begin; - $channel->close( - on_success => sub { $cv->end }, - on_failure => sub { $cv->end }, - ); + $cv->begin(); + + my @ids = keys %{$self->{_channels}}; + for my $id (@ids) { + my $channel = $self->{_channels}->{$id}; + if ($channel->is_open) { + $cv->begin(); + $channel->close( + on_success => sub { $cv->end() }, + on_failure => sub { $cv->end() }, + ); + } } - $cv->end; + $cv->end(); return $self; } @@ -424,15 +459,12 @@ sub _finish_close { my $self = shift; my %args = @_; - if (!$self->{_is_open}) { - $args{on_failure}->("Already closed"); + if (my @ch = map { $_->id } grep { defined() && $_->is_open } keys %{$self->{_channels}}) { + $args{on_failure}->("BUG: closing with channel(s) open: @ch"); return; } - if (my @ch = grep { my $ch = $self->{_channels}{$_}; defined($ch) && $ch->is_open } keys %{$self->{_channels}}) { - $args{on_failure}->("Can't close connection with channel(s) open: @ch"); - return; - } + $self->{_state} = _ST_CLOSED; $self->_push_write_and_read( 'Connection::Close', {}, 'Connection::CloseOk', @@ -448,8 +480,6 @@ sub _finish_close { }, ); - $self->{_is_open} = 0; - return; } @@ -579,7 +609,7 @@ sub _set_cbs { my %args = @_; $args{on_success} ||= sub {}; - $args{on_failure} ||= sub { return if in_global_destruction; die @_}; + $args{on_failure} ||= sub { die @_ unless in_global_destruction }; return %args; } @@ -588,7 +618,7 @@ sub _check_open { my $self = shift; my ($failure_cb) = @_; - return 1 if $self->{_is_open}; + return 1 if $self->is_open; $failure_cb->('Connection has already been closed'); return 0; From 97e3e46d15a6dd9a541eea2746987929cb775dbd Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Thu, 6 Jun 2013 16:01:58 -0700 Subject: [PATCH 061/165] add doc for close state fixes --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 7f8d5b3..80acd0d 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ active connections - on channel close, automatically call on_return callbacks for any publishes that are waiting + - maintain more state around opening and closing to avoid hang/race + when server sends Close after client does (this is possible!) - cope with AMQP quirk that in confirm mode, returned messages are *also* acked/nacked - document $channel->publish From f1fab1a99eee773fb92e9245d4454be3909a2a72 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Fri, 7 Jun 2013 08:54:27 +0100 Subject: [PATCH 062/165] Version 1.14 --- Changes | 2 +- lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 80acd0d..1def86e 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ - +1.14 Fri Jun 7 08:54:00 BST 2013 - Fix paper-bag bug in heartbeat - always lost heartbeat even on active connections - on channel close, automatically call on_return callbacks for any diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index dd2a5c6..35d6932 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.13'; +our $VERSION = '1.14'; use constant { _ST_CLOSED => 0, diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 48af542..ce23bab 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -18,7 +18,7 @@ use constant { _ST_OPEN => 2, }; -our $VERSION = '1.13'; +our $VERSION = '1.14'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 9a9469e..8f85360 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.13'; +our $VERSION = '1.14'; sub new { my $class = shift; From 063ba0378fc6f0b345bec05ad357fc86b84cfc52 Mon Sep 17 00:00:00 2001 From: Chip Salzenberg Date: Fri, 28 Jun 2013 17:42:06 -0700 Subject: [PATCH 063/165] Fix paper-bag bug in connection close - calling nonexistent method. --- Changes | 3 +++ lib/AnyEvent/RabbitMQ.pm | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 1def86e..3c98c3a 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ + + - Fix paper-bag bug in connection close - calling nonexistent method. + 1.14 Fri Jun 7 08:54:00 BST 2013 - Fix paper-bag bug in heartbeat - always lost heartbeat even on active connections diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 35d6932..4e41775 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -147,7 +147,7 @@ sub connect { my $self = $weak_self or return; if ($self->is_open) { - $self->_force_close($close_cb, $message); + $self->_server_closed($close_cb, $message); } else { $failure_cb->(@_); @@ -378,7 +378,7 @@ sub _start_heartbeat { elsif (++$idle_cycles > 1) { delete $self->{_heartbeat_timer}; $failure_cb->("Heartbeat lost"); - $self->_force_close($close_cb, "Heartbeat lost"); + $self->_server_closed($close_cb, "Heartbeat lost"); return; } $self->_push_write(Net::AMQP::Frame::Heartbeat->new()); From 42528b4cb58cf002b92f206ba8ebfc853eac60e0 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 1 Jul 2013 12:35:33 +0100 Subject: [PATCH 064/165] Version 1.15 --- Changes | 2 +- lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 3c98c3a..5c89ccd 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ - +1.15 Mon Jul 1 12:35:00 BST 2013 - Fix paper-bag bug in connection close - calling nonexistent method. 1.14 Fri Jun 7 08:54:00 BST 2013 diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 4e41775..f736a1e 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.14'; +our $VERSION = '1.15'; use constant { _ST_CLOSED => 0, diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index ce23bab..bb2c078 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -18,7 +18,7 @@ use constant { _ST_OPEN => 2, }; -our $VERSION = '1.14'; +our $VERSION = '1.15'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 8f85360..037820e 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.14'; +our $VERSION = '1.15'; sub new { my $class = shift; From 306a4ecbc807be2674dff6fcbe78292d6ccbd3a8 Mon Sep 17 00:00:00 2001 From: Mark Ellis Date: Wed, 11 Dec 2013 09:04:29 +0000 Subject: [PATCH 065/165] doc patch to fix what looks to be a circular reference --- lib/AnyEvent/RabbitMQ.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index f736a1e..8c25574 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -665,6 +665,7 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. tls => 0, # Or 1 if you'd like SSL tune => { heartbeat => 30, channel_max => $whatever, frame_max = $whatever }, on_success => sub { + my $ar = shift; $ar->open_channel( on_success => sub { my $channel = shift; From 33aabf67211289628d4382e327a8ba57eb640ffe Mon Sep 17 00:00:00 2001 From: Peter Haworth Date: Mon, 31 Mar 2014 11:11:23 +0100 Subject: [PATCH 066/165] Actually close things when $ar->close() is called There's still a circular reference somewhere preventing automatic destruction, but this at least removes the memory leak for explicit close --- lib/AnyEvent/RabbitMQ.pm | 6 +- xt/06_close.t | 159 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 xt/06_close.t diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 8c25574..6667cfc 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.15'; +our $VERSION = '1.1501'; use constant { _ST_CLOSED => 0, @@ -421,7 +421,7 @@ sub close { my $self = shift; my %args = $self->_set_cbs(@_); - if (!$self->{_state} == _ST_CLOSED) { + if ($self->{_state} == _ST_CLOSED) { $args{on_success}->(@_); return $self; } @@ -459,7 +459,7 @@ sub _finish_close { my $self = shift; my %args = @_; - if (my @ch = map { $_->id } grep { defined() && $_->is_open } keys %{$self->{_channels}}) { + if (my @ch = map { $_->id } grep { defined() && $_->is_open } values %{$self->{_channels}}) { $args{on_failure}->("BUG: closing with channel(s) open: @ch"); return; } diff --git a/xt/06_close.t b/xt/06_close.t new file mode 100644 index 0000000..e33317e --- /dev/null +++ b/xt/06_close.t @@ -0,0 +1,159 @@ +use Test::More; +use Test::Exception; + +my %conf = ( + host => 'localhost', + port => 5672, + user => 'guest', + pass => 'guest', + vhost => '/', +); + +eval { + use IO::Socket::INET; + + my $socket = IO::Socket::INET->new( + Proto => 'tcp', + PeerAddr => $conf{host}, + PeerPort => $conf{port}, + Timeout => 1, + ) or die 'Error connecting to AMQP Server!'; + + close $socket; +}; + +plan skip_all => 'Connection failure: ' + . $conf{host} . ':' . $conf{port} if $@; +#plan tests => 3; + +use AnyEvent::RabbitMQ; + +subtest 'No channels', sub { + my $ar = connect_ar(); + ok $ar->is_open, 'connection is open'; + is channel_count($ar), 0, 'no channels open'; + +#my @queues = map { +# my $ch = open_channel($ar); +# my $queue = 'test_q' . $_; +# declare_queue($ch, $queue,); +# +# my $done = AnyEvent->condvar; +# my $cdone = AnyEvent->condvar; +# consume($ch, $queue, sub { +# my $response = shift; +# return if 'stop' ne $response->{body}->payload; +# $done->send(); +# }, sub { +# $cdone->send(); +# }); +# {name => $queue, cv => $done, ccv => $cdone}; +#} (1..5); + +#pass('queue setup'); +# +#my $ch = open_channel($ar); +#for my $queue (@queues) { +# publish($ch, $queue->{name}, 'hello'); +# publish($ch, $queue->{name}, 'stop'); +#} +# +#my $count = 0; +#for my $queue (@queues) { +# $queue->{cv}->recv; +# $count++; +#} +# +#is($count, 5, 'consume count'); +# +#for my $queue (@queues) { +# delete_queue($ch, $queue->{name}); +#} +# +#my $ccount = 0; +#for my $queue (@queues) { +# $queue->{ccv}->recv; +# $ccount++; +#} +# +#is($ccount, 5, 'cancel count'); + + close_ar($ar); + ok !$ar->is_open, 'connection closed'; + is channel_count($ar), 0, 'no channels open'; +}; + +subtest 'channels', sub { + my $ar = connect_ar(); + ok $ar->is_open, 'connection is open'; + is channel_count($ar), 0, 'no channels open'; + + my $ch = open_channel($ar); + ok $ch->is_open, 'channel is open'; + is channel_count($ar), 1, 'no channels open'; + + close_ar($ar); + ok !$ar->is_open, 'connection closed'; + is channel_count($ar), 0, 'no channels open'; + ok !$ch->is_open, 'channel closed'; +}; + + + + +done_testing; + +sub connect_ar { + my $done = AnyEvent->condvar; + my $ar = AnyEvent::RabbitMQ->new()->load_xml_spec()->connect( + (map {$_ => $conf{$_}} qw(host port user pass vhost)), + timeout => 1, + on_success => sub {$done->send(1)}, + on_failure => sub { diag @_; $done->send()}, + on_close => \&handle_close, + ); + die 'Connection failure' if !$done->recv; + return $ar; +} + +sub close_ar { + my ($ar,) = @_; + + my $done = AnyEvent->condvar; + $ar->close( + on_success => sub {$done->send(1)}, + on_failure => sub { diag @_; $done->send()}, + ); + die 'Close failure' if !$done->recv; + + return; +} + +sub channel_count { + my ($ar,) = @_; + + return scalar keys %{$ar->channels}; +} + +sub open_channel { + my ($ar,) = @_; + + my $done = AnyEvent->condvar; + $ar->open_channel( + on_success => sub {$done->send(shift)}, + on_failure => sub {$done->send()}, + on_return => sub {die 'Receive return'}, + on_close => \&handle_close, + ); + my $ch = $done->recv; + die 'Open channel failure' if !$ch; + + return $ch; +} + +sub handle_close { + my $method_frame = shift->method_frame; + die $method_frame->reply_code, $method_frame->reply_text + if $method_frame->reply_code; +} + From 8057fda5237b561629975ef006afb1f17c1b4b7c Mon Sep 17 00:00:00 2001 From: Peter Haworth Date: Mon, 31 Mar 2014 11:16:34 +0100 Subject: [PATCH 067/165] Tiny doc tweaks for missing commas --- lib/AnyEvent/RabbitMQ.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 6667cfc..f3cce1a 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -681,7 +681,7 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. on_close => sub { my $method_frame = shift->method_frame; die $method_frame->reply_code, $method_frame->reply_text; - } + }, ); }, on_failure => $cv, @@ -689,7 +689,7 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. on_return => sub { my $frame = shift; die "Unable to deliver ", Dumper($frame); - } + }, on_close => sub { my $why = shift; if (ref($why)) { From 39d7c64f0f4d5f941ee893eb7409b0adf07b720f Mon Sep 17 00:00:00 2001 From: Peter Haworth Date: Mon, 31 Mar 2014 11:20:14 +0100 Subject: [PATCH 068/165] Tidy up close() test --- xt/06_close.t | 52 +-------------------------------------------------- 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/xt/06_close.t b/xt/06_close.t index e33317e..c78fe22 100644 --- a/xt/06_close.t +++ b/xt/06_close.t @@ -24,7 +24,7 @@ eval { plan skip_all => 'Connection failure: ' . $conf{host} . ':' . $conf{port} if $@; -#plan tests => 3; +plan tests => 2; use AnyEvent::RabbitMQ; @@ -33,51 +33,6 @@ subtest 'No channels', sub { ok $ar->is_open, 'connection is open'; is channel_count($ar), 0, 'no channels open'; -#my @queues = map { -# my $ch = open_channel($ar); -# my $queue = 'test_q' . $_; -# declare_queue($ch, $queue,); -# -# my $done = AnyEvent->condvar; -# my $cdone = AnyEvent->condvar; -# consume($ch, $queue, sub { -# my $response = shift; -# return if 'stop' ne $response->{body}->payload; -# $done->send(); -# }, sub { -# $cdone->send(); -# }); -# {name => $queue, cv => $done, ccv => $cdone}; -#} (1..5); - -#pass('queue setup'); -# -#my $ch = open_channel($ar); -#for my $queue (@queues) { -# publish($ch, $queue->{name}, 'hello'); -# publish($ch, $queue->{name}, 'stop'); -#} -# -#my $count = 0; -#for my $queue (@queues) { -# $queue->{cv}->recv; -# $count++; -#} -# -#is($count, 5, 'consume count'); -# -#for my $queue (@queues) { -# delete_queue($ch, $queue->{name}); -#} -# -#my $ccount = 0; -#for my $queue (@queues) { -# $queue->{ccv}->recv; -# $ccount++; -#} -# -#is($ccount, 5, 'cancel count'); - close_ar($ar); ok !$ar->is_open, 'connection closed'; is channel_count($ar), 0, 'no channels open'; @@ -98,11 +53,6 @@ subtest 'channels', sub { ok !$ch->is_open, 'channel closed'; }; - - - -done_testing; - sub connect_ar { my $done = AnyEvent->condvar; my $ar = AnyEvent::RabbitMQ->new()->load_xml_spec()->connect( From 01528732660a39bcca071219ee7c81edec118eb2 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Sat, 12 Apr 2014 14:43:03 +0100 Subject: [PATCH 069/165] Version 1.16 --- Changes | 4 ++++ lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 5c89ccd..6f6cedc 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.16 Sat Apr 12 14:42:00 BST 2014 + - Doc fixes (Mark Ellis) + - Fix leak when calling ->close + tests (Peter Haworth) + 1.15 Mon Jul 1 12:35:00 BST 2013 - Fix paper-bag bug in connection close - calling nonexistent method. diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index f3cce1a..6d97402 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.1501'; +our $VERSION = '1.16'; use constant { _ST_CLOSED => 0, diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index bb2c078..af4d201 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -18,7 +18,7 @@ use constant { _ST_OPEN => 2, }; -our $VERSION = '1.15'; +our $VERSION = '1.16'; sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 037820e..53f132f 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,7 +3,7 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.15'; +our $VERSION = '1.16'; sub new { my $class = shift; From ea490e185e8524b6e78994c139045b9e4bac9f96 Mon Sep 17 00:00:00 2001 From: William Cox Date: Tue, 6 May 2014 23:06:22 -0400 Subject: [PATCH 070/165] Chunk up payload into discrete Body frames During Tune/Ok a frame_max is negotiated. The server will drop connections that publish larger payloads than this. Use this frame_max to slice up the payload into appropriately sized Body frames. --- lib/AnyEvent/RabbitMQ.pm | 8 ++++++++ lib/AnyEvent/RabbitMQ/Channel.pm | 21 +++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 6d97402..9e5ef05 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -55,6 +55,8 @@ sub new { _channels => {}, _login_user => '', _server_properties => {}, + _frame_max => undef, + _body_max => undef, }, $class; } @@ -345,6 +347,12 @@ sub _tune { ( $_ => defined($t) ? $t : $frame->method_frame->$_ ) } qw( channel_max frame_max heartbeat ); + $self->{_frame_max} = $tune{frame_max}; + if ($self->{_frame_max}) { + # calculate how big the body can actually be + $self->{_body_max} = $self->{_frame_max} - Net::AMQP::_HEADER_LEN - Net::AMQP::_FOOTER_LEN; + } + $self->_push_write( Net::AMQP::Protocol::Connection::TuneOk->new(%tune,) ); diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index af4d201..9edcebe 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -8,6 +8,7 @@ use AnyEvent; use Scalar::Util qw( looks_like_number weaken ); use Devel::GlobalDestruction; use Carp qw(croak); +use POSIX qw(ceil); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } use namespace::clean; @@ -476,10 +477,22 @@ sub _header { sub _body { my ($self, $body,) = @_; - $self->{connection}->_push_write( - Net::AMQP::Frame::Body->new(payload => $body), - $self->{id}, - ); + if (my $body_max = $self->{connection}->{_body_max}) { + # chunk up body into segments measured by $frame_max + while (length $body) { + $self->{connection}->_push_write( + Net::AMQP::Frame::Body->new( + payload => substr($body, 0, $body_max, '')), + $self->{id} + ); + } + } else { + # frame_max is either un-negotiated (unlikely) or 0 (unlimited) + $self->{connection}->_push_write( + Net::AMQP::Frame::Body->new(payload => $body), + $self->{id} + ); + } return $self; } From 83232079c25442fb1cc48354f35dbab4116f64a2 Mon Sep 17 00:00:00 2001 From: William Cox Date: Wed, 7 May 2014 22:22:06 -0400 Subject: [PATCH 071/165] negotiate tune parameters with the server --- lib/AnyEvent/RabbitMQ.pm | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 9e5ef05..d60ee2c 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -343,12 +343,19 @@ sub _tune { my $self = $weak_self or return; my $frame = shift; - my %tune = map { my $t = $args{tune}{$_}; - ( $_ => defined($t) ? $t : $frame->method_frame->$_ ) } - qw( channel_max frame_max heartbeat ); + my %tune; + foreach (qw( channel_max frame_max heartbeat )) { + my $client = $args{tune}{$_} || 0; + my $server = $frame->method_frame->$_ || 0; + + # negotiate with the server such that we cannot request a larger + # value set by the server, unless the server said unlimited + $tune{$_} = ($server == 0 or $client == 0) + ? ($server > $client ? $server : $client) # max + : ($client > $server ? $server : $client); # min + } - $self->{_frame_max} = $tune{frame_max}; - if ($self->{_frame_max}) { + if ($self->{_frame_max} = $tune{frame_max}) { # calculate how big the body can actually be $self->{_body_max} = $self->{_frame_max} - Net::AMQP::_HEADER_LEN - Net::AMQP::_FOOTER_LEN; } From 5b4b8535b0757e3074bfa7b0af0a725162783dc9 Mon Sep 17 00:00:00 2001 From: William Cox Date: Wed, 7 May 2014 22:26:20 -0400 Subject: [PATCH 072/165] simplify body fragmenting --- lib/AnyEvent/RabbitMQ/Channel.pm | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 9edcebe..b5bb3b2 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -477,19 +477,13 @@ sub _header { sub _body { my ($self, $body,) = @_; - if (my $body_max = $self->{connection}->{_body_max}) { - # chunk up body into segments measured by $frame_max - while (length $body) { - $self->{connection}->_push_write( - Net::AMQP::Frame::Body->new( - payload => substr($body, 0, $body_max, '')), - $self->{id} - ); - } - } else { - # frame_max is either un-negotiated (unlikely) or 0 (unlimited) + my $body_max = $self->{connection}->{_body_max} || length $body; + + # chunk up body into segments measured by $frame_max + while (length $body) { $self->{connection}->_push_write( - Net::AMQP::Frame::Body->new(payload => $body), + Net::AMQP::Frame::Body->new( + payload => substr($body, 0, $body_max, '')), $self->{id} ); } From ee6442469f152a78d2e5f645d0e0b954511c0a09 Mon Sep 17 00:00:00 2001 From: William Cox Date: Thu, 8 May 2014 20:41:11 -0400 Subject: [PATCH 073/165] test messages are getting chunked up --- xt/04_anyevent.t | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index 40c6472..e4dbd5f 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -45,6 +45,7 @@ lives_ok sub { my $done = AnyEvent->condvar; $ar->connect( (map {$_ => $conf{$_}} qw(host port user pass vhost)), + tune => { frame_max => 2**17 }, timeout => 1, on_success => sub { my $ar = shift; @@ -179,7 +180,7 @@ $ch->get( ); $done->recv; -for my $size (10, 131_064, 10) { +for my $size (10, 131_064, 10, 140_000) { send_large_size_message($ch, $size); } From 335b4b0077b12bc08ca8f643b48581260bca36ed Mon Sep 17 00:00:00 2001 From: William Cox Date: Thu, 8 May 2014 20:48:19 -0400 Subject: [PATCH 074/165] use negotiated channel_max --- lib/AnyEvent/RabbitMQ.pm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index d60ee2c..92c1a23 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -44,6 +44,8 @@ use constant { Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; +Readonly my $DEFAULT_CHANNEL_MAX => 2**16; + sub new { my $class = shift; return bless { @@ -57,6 +59,7 @@ sub new { _server_properties => {}, _frame_max => undef, _body_max => undef, + _channel_max => undef, }, $class; } @@ -360,6 +363,8 @@ sub _tune { $self->{_body_max} = $self->{_frame_max} - Net::AMQP::_HEADER_LEN - Net::AMQP::_FOOTER_LEN; } + $self->{_channel_max} = $tune{channel_max} || $DEFAULT_CHANNEL_MAX; + $self->_push_write( Net::AMQP::Protocol::Connection::TuneOk->new(%tune,) ); @@ -498,8 +503,6 @@ sub _finish_close { return; } -use constant _MAX_CHANID => 0xFFFF; - sub open_channel { my $self = shift; my %args = $self->_set_cbs(@_); @@ -516,8 +519,8 @@ sub open_channel { if (!$id) { my $try_id = $self->{_last_chan_id}; - for (1 .. 2**16) { - $try_id = 1 if ++$try_id > _MAX_CHANID; + for (1 .. $self->{_channel_max}) { + $try_id = 1 if ++$try_id > $self->{_channel_max}; unless (defined $self->{_channels}->{$try_id}) { $id = $try_id; last; From 17edf797c9510efea8dce8215d942954e2d7b446 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Fri, 25 Jul 2014 14:02:14 -0700 Subject: [PATCH 075/165] Changelog --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index 6f6cedc..6e7276e 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Add support for chunking large bodies into multiple AMQP frames, + allowing the sending of large messages. + 1.16 Sat Apr 12 14:42:00 BST 2014 - Doc fixes (Mark Ellis) - Fix leak when calling ->close + tests (Peter Haworth) From 67fbe926b97e72b1ae087a7f09d44a20cdfcd603 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Fri, 25 Jul 2014 14:03:19 -0700 Subject: [PATCH 076/165] Checking in changes prior to tagging of version 1.17. Changelog diff is: diff --git a/Changes b/Changes index 6e7276e..5578b4e 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.17 Fri Jul 25 14:02:00 PDT 2014 - Add support for chunking large bodies into multiple AMQP frames, allowing the sending of large messages. --- .shipit | 2 +- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.shipit b/.shipit index dba08a4..bcb93b5 100644 --- a/.shipit +++ b/.shipit @@ -1,2 +1,2 @@ steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist -svk.tagpattern = release-%v +git.tagpattern = %v diff --git a/Changes b/Changes index 6e7276e..5578b4e 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.17 Fri Jul 25 14:02:00 PDT 2014 - Add support for chunking large bodies into multiple AMQP frames, allowing the sending of large messages. diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 92c1a23..41d2333 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.16'; +our $VERSION = '1.17'; use constant { _ST_CLOSED => 0, From 5fcd8a1f45a1f2fd9f46eb2de21d741c08e497e5 Mon Sep 17 00:00:00 2001 From: Keith James Date: Fri, 12 Sep 2014 12:21:45 +0100 Subject: [PATCH 077/165] Added support for the RabbitMQ bind_exchange/unbind_exchange extension to AMQP 0-9-1 (which is already provided for in the included XML spec). --- lib/AnyEvent/RabbitMQ/Channel.pm | 88 ++++++++++++++++++++++++++++---- xt/04_anyevent.t | 49 +++++++++++++++++- 2 files changed, 124 insertions(+), 13 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index b5bb3b2..08ee80f 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -220,7 +220,51 @@ sub declare_exchange { ticket => 0, nowait => 0, # FIXME }, - 'Exchange::DeclareOk', + 'Exchange::DeclareOk', + $cb, + $failure_cb, + $self->{id}, + ); + + return $self; +} + +sub bind_exchange { + my $self = shift; + my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); + + return $self if !$self->_check_open($failure_cb); + + $self->{connection}->_push_write_and_read( + 'Exchange::Bind', + { + %args, # source, destination, routing_key + ticket => 0, + nowait => 0, # FIXME + }, + 'Exchange::BindOk', + $cb, + $failure_cb, + $self->{id}, + ); + + return $self; +} + +sub unbind_exchange { + my $self = shift; + my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); + + return $self if !$self->_check_open($failure_cb); + + $self->{connection}->_push_write_and_read( + 'Exchange::Unbind', + { + %args, # source, destination, routing_key + ticket => 0, + nowait => 0, # FIXME + }, + 'Exchange::UnbindOk', $cb, $failure_cb, $self->{id}, @@ -243,7 +287,7 @@ sub delete_exchange { ticket => 0, nowait => 0, # FIXME }, - 'Exchange::DeleteOk', + 'Exchange::DeleteOk', $cb, $failure_cb, $self->{id}, @@ -271,7 +315,7 @@ sub declare_queue { ticket => 0, nowait => 0, # FIXME }, - 'Queue::DeclareOk', + 'Queue::DeclareOk', $cb, $failure_cb, $self->{id}, @@ -291,7 +335,7 @@ sub bind_queue { ticket => 0, nowait => 0, # FIXME }, - 'Queue::BindOk', + 'Queue::BindOk', $cb, $failure_cb, $self->{id}, @@ -312,7 +356,7 @@ sub unbind_queue { %args, # queue, exchange, routing_key ticket => 0, }, - 'Queue::UnbindOk', + 'Queue::UnbindOk', $cb, $failure_cb, $self->{id}, @@ -334,7 +378,7 @@ sub purge_queue { ticket => 0, nowait => 0, # FIXME }, - 'Queue::PurgeOk', + 'Queue::PurgeOk', $cb, $failure_cb, $self->{id}, @@ -358,7 +402,7 @@ sub delete_queue { ticket => 0, nowait => 0, # FIXME }, - 'Queue::DeleteOk', + 'Queue::DeleteOk', $cb, $failure_cb, $self->{id}, @@ -582,7 +626,7 @@ sub get { %args, # queue ticket => 0, }, - [qw(Basic::GetOk Basic::GetEmpty)], + [qw(Basic::GetOk Basic::GetEmpty)], sub { my $frame = shift; return $cb->({empty => $frame}) @@ -630,7 +674,7 @@ sub qos { prefetch_size => 0, global => 0, }, - 'Basic::QosOk', + 'Basic::QosOk', $cb, $failure_cb, $self->{id}, @@ -1028,6 +1072,30 @@ The name of the exchange =back +=head2 bind_exchange + +Binds an exchange to another exchange, with a routing key. + +Arguments: + +=over + +=item source + +The name of the source exchange to bind + +=item destination + +The name of the destination exchange to bind + +=item routing_key + +The routing key to bind with + +=back + +=head2 unbind_exchange + =head2 delete_exchange =head2 declare_queue @@ -1251,5 +1319,3 @@ the server, so the on_ack callback of publish works. See L for author(s), copyright and license. =cut - - diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index e4dbd5f..9cc0289 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -101,6 +101,29 @@ $ch->declare_exchange( ); $done->recv; +$done = AnyEvent->condvar; +$ch->declare_exchange( + exchange => 'test_x_dest', + on_success => sub { + pass('declare destination exchange'); + $done->send; + }, + on_failure => failure_cb($done), +); +$done->recv; + +$done = AnyEvent->condvar; +$ch->bind_exchange( + source => 'test_x', + destination => 'test_x_dest', + on_success => sub { + pass('bind exchange -> dest'); + $done->send; + }, + on_failure => failure_cb($done), +); +$done->recv; + $done = AnyEvent->condvar; $ch->declare_queue( queue => 'test_q', @@ -270,7 +293,7 @@ $ch->cancel( on_failure => failure_cb($done), ); $done->recv; - + $done = AnyEvent->condvar; my $recover_count = 0; $ch->consume( @@ -430,6 +453,18 @@ $ch->delete_queue( ); $done->recv; +$done = AnyEvent->condvar; +$ch->unbind_exchange( + source => 'test_x', + destination => 'test_x_dest', + on_success => sub { + pass('unbind exchange'); + $done->send; + }, + on_failure => failure_cb($done), +); +$done->recv; + $done = AnyEvent->condvar; $ch->delete_exchange( exchange => 'test_x', @@ -441,6 +476,17 @@ $ch->delete_exchange( ); $done->recv; +$done = AnyEvent->condvar; +$ch->delete_exchange( + exchange => 'test_x_dest', + on_success => sub { + pass('delete destination exchange'); + $done->send; + }, + on_failure => failure_cb($done), +); +$done->recv; + $done = AnyEvent->condvar; $ar->close( on_success => sub { @@ -496,4 +542,3 @@ sub send_large_size_message { } done_testing; - From cc636dc33917aa5bc64d537f05ff6eaef67ac1fe Mon Sep 17 00:00:00 2001 From: Keith James Date: Fri, 12 Sep 2014 16:25:04 +0100 Subject: [PATCH 078/165] Trigger first Travis build --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..764843f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: perl + +perl: + - "5.16" + +services: + -rabbitmq From 107dbaa23d63998a764fa7af4c26ed7d60c7fdea Mon Sep 17 00:00:00 2001 From: Keith James Date: Fri, 12 Sep 2014 19:29:00 +0100 Subject: [PATCH 079/165] Override the failed default Travis cpanm setup Requires Module::Install::AuthorTests Requires Readonly, Net::AMQP, AnyEvent, Devel::GlobalDestruction Add Test::Spelling and Test::Perl::Critic to activate the remaining tests. --- .travis.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 764843f..239641f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,13 @@ perl: - "5.16" services: - -rabbitmq + - rabbitmq + +install: + - cpanm --quiet --notest Module::Install::AuthorTests + - cpanm --quiet --notest Test::Spelling + - cpanm --quiet --notest Test::Perl::Critic + - cpanm --quiet --notest Readonly + - cpanm --quiet --notest Net::AMQP + - cpanm --quiet --notest AnyEvent + - cpanm --quiet --notest Devel::GlobalDestruction From 83307709b455a9f6c3bf382871a1a87f35d9c573 Mon Sep 17 00:00:00 2001 From: Keith James Date: Fri, 12 Sep 2014 20:05:34 +0100 Subject: [PATCH 080/165] Finalised the Travis setup with tests passing. --- .travis.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 239641f..1edee45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,15 @@ perl: services: - rabbitmq +before_install: + - sudo apt-get update -qq + - sudo apt-get install -y aspell + install: - - cpanm --quiet --notest Module::Install::AuthorTests - - cpanm --quiet --notest Test::Spelling - - cpanm --quiet --notest Test::Perl::Critic - - cpanm --quiet --notest Readonly - - cpanm --quiet --notest Net::AMQP - cpanm --quiet --notest AnyEvent - cpanm --quiet --notest Devel::GlobalDestruction + - cpanm --quiet --notest Module::Install::AuthorTests + - cpanm --quiet --notest Net::AMQP + - cpanm --quiet --notest Readonly + - cpanm --quiet --notest Test::Perl::Critic + - cpanm --quiet --notest Test::Spelling From 6f3124c9ad1158ff7ff5d33b463bf49829a5252c Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Sat, 27 Sep 2014 23:25:06 -0700 Subject: [PATCH 081/165] Changelog --- Changes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changes b/Changes index 5578b4e..78332ea 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for Perl extension AnyEvent::RabbitMQ + + - Added the bind_exchange and unbind_exchange methods + for exchange-exchange bindings. + 1.17 Fri Jul 25 14:02:00 PDT 2014 - Add support for chunking large bodies into multiple AMQP frames, allowing the sending of large messages. From 109c1197cde526c1ea5caae31f45ef203c34a2a7 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Mon, 29 Sep 2014 19:39:24 -0700 Subject: [PATCH 082/165] Checking in changes prior to tagging of version 1.18. Changelog diff is: diff --git a/Changes b/Changes index 78332ea..68f0830 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ - +1.18 Mon Sep 29 19:36:00 PDT 2014 - Added the bind_exchange and unbind_exchange methods for exchange-exchange bindings. --- Changes | 2 +- MANIFEST.SKIP | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 78332ea..68f0830 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ - +1.18 Mon Sep 29 19:36:00 PDT 2014 - Added the bind_exchange and unbind_exchange methods for exchange-exchange bindings. diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 3c25ee8..4d3361c 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -20,3 +20,4 @@ ^[^/]+\.pl$ ^\.shipit$ ^\.gitignore$ +^\.travis.yml$ diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 41d2333..fd160f1 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.17'; +our $VERSION = '1.18'; use constant { _ST_CLOSED => 0, From cdc56baab65e8e165e47657205ee3793d4b65939 Mon Sep 17 00:00:00 2001 From: Moritz Lenz Date: Tue, 11 Nov 2014 13:11:38 +0100 Subject: [PATCH 083/165] Fill in some missing docs --- lib/AnyEvent/RabbitMQ.pm | 3 ++ lib/AnyEvent/RabbitMQ/Channel.pm | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index fd160f1..1872047 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -734,6 +734,9 @@ You can use AnyEvent::RabbitMQ to - * Publish, consume, get, ack, recover and reject messages * Select, commit and rollback transactions +Most of these actions can be done through L. +Please see the documentation there for more details. + AnyEvent::RabbitMQ is known to work with RabbitMQ versions 2.5.1 and versions 0-8 and 0-9-1 of the AMQP specification. This client is the non-blocking version, for a blocking version with a similar API, see L. diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 08ee80f..da80f7e 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -1015,6 +1015,11 @@ AnyEvent::RabbitMQ::Channel - Abstraction of an AMQP channel. =head1 DESCRIPTION +A RabbitMQ channel. + +A channel is a light-weight virtual connection within a TCP connection to a +RabbitMQ broker. + =head1 ARGUMENTS FOR C =over @@ -1100,6 +1105,59 @@ The routing key to bind with =head2 declare_queue +Declare a queue, that is, create it if it doesn't exist yet. + +Arguments: + +=over + +=item queue + +Name of the queue to be declared. If the queue name is the empty string, +RabbitMQ will create a unique name for the queue. This is useful for +temporary/private reply queues. + +=item on_success + +Callback that is called when the queue was declared successfully. The argument +to the callback is of type L. To get the name of the +Queue (if you declared it with an empty name), you can say + + on_success => sub { + my $method = shift; + my $name = $method->method_frame->queue; + }; + +=item on_failure + +Callback that is called when the declaration of the queue has failed. + +=item auto_delete + +0 or 1, default 0 + +=item passive + +0 or 1, default 0 + +=item durable + +0 or 1, default 0 + +=item exclusive + +0 or 1, default 0 + +=item no_ack + +0 or 1, default 1 + +=item ticket + +default 0 + +=back + =head2 bind_queue Binds a queue to an exchange, with a routing key. @@ -1170,10 +1228,18 @@ Arguments: =over +=item queue + +The name of the queue to be consumed from. + =item on_consume Callback called with an argument of the message which has been consumed. +The message is a hash reference, where the value to key C
is an object +of type L, L is a +L, and C a L. + =item on_cancel Callback called if consumption is canceled. This may be at client request From 967ecd16af17be198209047fecd76d6963acaa34 Mon Sep 17 00:00:00 2001 From: Dave Mueller Date: Tue, 17 Mar 2015 09:33:31 -0400 Subject: [PATCH 084/165] Added 'no_ack' as an optional arument to AnyEvent::RabbitMQ::Channel->consume method --- lib/AnyEvent/RabbitMQ/Channel.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index da80f7e..fa8ba29 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -543,13 +543,14 @@ sub consume { my $consumer_cb = delete $args{on_consume} || sub {}; my $cancel_cb = delete $args{on_cancel} || sub {}; + my $no_ack = delete $args{no_ack} || 1; $self->{connection}->_push_write_and_read( 'Basic::Consume', { consumer_tag => '', no_local => 0, - no_ack => 1, + no_ack => $no_ack, exclusive => 0, %args, # queue From a9409d83d84affcb26101d97b184d0a4feb8a2ce Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 21 Mar 2015 16:43:06 +0000 Subject: [PATCH 085/165] Allow no_ack to be set to zero. --- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index fa8ba29..f55f635 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -543,7 +543,7 @@ sub consume { my $consumer_cb = delete $args{on_consume} || sub {}; my $cancel_cb = delete $args{on_cancel} || sub {}; - my $no_ack = delete $args{no_ack} || 1; + my $no_ack = delete $args{no_ack} // 1; $self->{connection}->_push_write_and_read( 'Basic::Consume', From e6b2a2c5a6c7303ce1ee6952ad81afb4971631be Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 21 Mar 2015 16:43:36 +0000 Subject: [PATCH 086/165] Tidy up white space. --- lib/AnyEvent/RabbitMQ/Channel.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index f55f635..df9501f 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -541,9 +541,9 @@ sub consume { return $self if !$self->_check_open($failure_cb); - my $consumer_cb = delete $args{on_consume} || sub {}; - my $cancel_cb = delete $args{on_cancel} || sub {}; - my $no_ack = delete $args{no_ack} // 1; + my $consumer_cb = delete $args{on_consume} || sub {}; + my $cancel_cb = delete $args{on_cancel} || sub {}; + my $no_ack = delete $args{no_ack} // 1; $self->{connection}->_push_write_and_read( 'Basic::Consume', From 598d1bed05bbd6bd2669bd36c9715e380cfd416f Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 21 Mar 2015 16:45:12 +0000 Subject: [PATCH 087/165] Add perldoc for no_ack. --- lib/AnyEvent/RabbitMQ/Channel.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index df9501f..25c8eb7 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -1260,6 +1260,11 @@ Callback called if the subscription was successful (before the first message is Callback called if the subscription fails for any reason. +=item no_ack + +Pass through the C flag. Defaults to C<1>. If set to C<1>, the server +will not expect messages to be acknowledged. + =back =head2 publish From f06a7205162fe7f1c419f1b8a0e75920daa42517 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 21 Mar 2015 16:53:32 +0000 Subject: [PATCH 088/165] Cut a new release! --- Changes | 8 ++++++-- lib/AnyEvent/RabbitMQ.pm | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 68f0830..0bd675e 100644 --- a/Changes +++ b/Changes @@ -1,8 +1,12 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.19 Sat Mar 21 16:49:24 GMT 2015 + - Add 'no_ack' as an optional argument to the ->consume method + (Dave Mueller). + - Fill in some missing documentation (Moritz Lenz). 1.18 Mon Sep 29 19:36:00 PDT 2014 - - Added the bind_exchange and unbind_exchange methods - for exchange-exchange bindings. + - Added the bind_exchange and unbind_exchange methods + for exchange-exchange bindings. 1.17 Fri Jul 25 14:02:00 PDT 2014 - Add support for chunking large bodies into multiple AMQP frames, diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 1872047..13a55db 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.18'; +our $VERSION = '1.19'; use constant { _ST_CLOSED => 0, From 4b3d542a239d8f7913758ea1b0d6624cd74f56a6 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 21 Mar 2015 17:02:54 +0000 Subject: [PATCH 089/165] I added defined-or. --- Makefile.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.PL b/Makefile.PL index c60be3f..ed155cf 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -15,7 +15,7 @@ tests 't/*.t'; author_tests 'xt'; install_share; -perl_version '5.006'; +perl_version '5.10'; build_requires 'Test::More'; build_requires 'Test::Exception'; build_requires 'version'; From e8d96875fb20feac2a522cb1b535deaf627cb403 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 22 Mar 2015 15:48:57 +0000 Subject: [PATCH 090/165] Remove stale version declarations. --- lib/AnyEvent/RabbitMQ/Channel.pm | 2 -- lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 25c8eb7..edadade 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -19,8 +19,6 @@ use constant { _ST_OPEN => 2, }; -our $VERSION = '1.16'; - sub new { my $class = shift; diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index 53f132f..de5423f 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,8 +3,6 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; -our $VERSION = '1.16'; - sub new { my $class = shift; return bless { From 2a18b5f8b826ab17f728670a41e9caaea28f1250 Mon Sep 17 00:00:00 2001 From: William Taylor Date: Wed, 13 May 2015 15:46:36 -0700 Subject: [PATCH 091/165] moving args to the bottom so global qos can be applied also. You can call qos multiple times. See: https://www.rabbitmq.com/consumer-prefetch.html --- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index edadade..dd02afb 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -669,9 +669,9 @@ sub qos { 'Basic::Qos', { prefetch_count => 1, - %args, prefetch_size => 0, global => 0, + %args, }, 'Basic::QosOk', $cb, From 8d1d5fc57edde00d1edc4081f5b86ce40472c2bb Mon Sep 17 00:00:00 2001 From: Denis Shirokov Date: Wed, 4 Nov 2015 23:17:33 +0200 Subject: [PATCH 092/165] set correct _state when connect unsuccesful --- lib/AnyEvent/RabbitMQ.pm | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 13a55db..03bfd3a 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -139,9 +139,14 @@ sub connect { undef $conn; my $self = $weak_self or return; - my $fh = shift or return $args{on_failure}->( - sprintf('Error connecting to AMQP Server %s:%s: %s', $args{host}, $args{port}, $!) - ); + my $fh = shift; + + unless ($fh) { + $self->{_state} = _ST_CLOSED; + return $args{on_failure}->( + sprintf('Error connecting to AMQP Server %s:%s: %s', $args{host}, $args{port}, $!) + ); + } my $close_cb = $args{on_close}; my $failure_cb = $args{on_failure}; From 491ab1b1372ab8b481c5029cce5c44590735175b Mon Sep 17 00:00:00 2001 From: Ruslan Zakirov Date: Sun, 28 Feb 2016 15:54:23 +0300 Subject: [PATCH 093/165] Merge together splitted into two publish documentation publish was documented twice with different level of details --- lib/AnyEvent/RabbitMQ/Channel.pm | 40 +++++++------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index dd02afb..f5c5478 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -1189,36 +1189,6 @@ Flushes the contents of a queue. Deletes a queue. The queue may not have any active consumers. -=head2 publish - -Publish a message to an exchange - -Arguments: - -=over - -=item body - -The text body of the message to send. - -=item header - -Customer headers for the message (if any). - -=item exchange - -The name of the exchange to send the message to. - -=item routing_key - -The routing key with which to publish the message. - -=item on_ack - -Callback (if any) for confirming acknowledgment when in confirm mode. - -=back - =head2 consume Subscribe to consume messages from a queue. @@ -1273,6 +1243,14 @@ Arguments: =over +=item exchange + +The name of the exchange to send the message to. + +=item routing_key + +The routing key with which to publish the message. + =item header Hash of AMQP message header info, including the confusingly similar element "headers", @@ -1280,7 +1258,7 @@ which may contain arbitrary string key/value pairs. =item body -Message body. +The text body of the message to send. =item mandatory From f3d04d3689bc780257c3e0ef086db00b8a4efeb7 Mon Sep 17 00:00:00 2001 From: Ruslan Zakirov Date: Fri, 12 Aug 2016 23:50:06 +0300 Subject: [PATCH 094/165] AMQP protocol permits bodyless messages "Certain methods (such as Basic.Publish, Basic.Deliver, etc.) are formally defined as carrying content. When a peer sends such a method frame, it always follows it with a content header and *zero* or more content body frames." So don't push body frame parser immediately, but only when header says that body size is greater that zero. --- lib/AnyEvent/RabbitMQ/Channel.pm | 55 +++++++++++++++++--------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index f5c5478..9ed791c 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -913,31 +913,13 @@ sub _push_read_header_and_body { my ($type, $frame, $cb, $failure_cb,) = @_; my $response = {$type => $frame}; my $body_size = 0; - - $self->{_content_queue}->get(sub{ - my $frame = shift; - - return $failure_cb->('Received data is not header frame') - if !$frame->isa('Net::AMQP::Frame::Header'); - - my $header_frame = $frame->header_frame; - return $failure_cb->( - 'Header is not Protocol::Basic::ContentHeader' - . 'Header was ' . ref $header_frame - ) if !$header_frame->isa('Net::AMQP::Protocol::Basic::ContentHeader'); - - $response->{header} = $header_frame; - $body_size = $frame->body_size; - }); + my $body_payload = ""; weaken(my $wcontq = $self->{_content_queue}); - my $body_payload = ""; - my $w_next_frame; - my $next_frame = sub { + my $w_body_frame; + my $body_frame = sub { my $frame = shift; - my $contq = $wcontq or return; - return $failure_cb->('Received data is not body frame') if !$frame->isa('Net::AMQP::Frame::Body'); @@ -945,7 +927,8 @@ sub _push_read_header_and_body { if (length($body_payload) < $body_size) { # More to come - $contq->get($w_next_frame); + my $contq = $wcontq or return; + $contq->get($w_body_frame); } else { $frame->payload($body_payload); @@ -953,10 +936,32 @@ sub _push_read_header_and_body { $cb->($response); } }; - $w_next_frame = $next_frame; - weaken($w_next_frame); + $w_body_frame = $body_frame; + weaken($w_body_frame); - $self->{_content_queue}->get($next_frame); + $self->{_content_queue}->get(sub{ + my $frame = shift; + + return $failure_cb->('Received data is not header frame') + if !$frame->isa('Net::AMQP::Frame::Header'); + + my $header_frame = $frame->header_frame; + return $failure_cb->( + 'Header is not Protocol::Basic::ContentHeader' + . 'Header was ' . ref $header_frame + ) if !$header_frame->isa('Net::AMQP::Protocol::Basic::ContentHeader'); + + $response->{header} = $header_frame; + + $body_size = $frame->body_size; + if ( $body_size ) { + my $contq = $wcontq or return; + $contq->get($body_frame); + } else { + $response->{body} = undef; + $cb->($response); + } + }); return $self; } From 6d6759c1d241a2274b64c28cd4cfdcd22e483633 Mon Sep 17 00:00:00 2001 From: Ruslan Zakirov Date: Tue, 27 Jun 2017 23:11:13 +0300 Subject: [PATCH 095/165] old check was triggering only if on_return is provided mixing '!/&&/||' and 'or/and/not' in one condition always bad idea. Mixing 'and' with 'or' without parens even worse. Old code was triggering error in one case as it works like this: unless ( $confirm ) { $ack or ( $nack or ( $return and $croak ) ) } So if we are not in confirm mode then croak if and only if return callback is set, but not the others. Use two conditions with nested block instead. I find it more readable as well as correct. --- lib/AnyEvent/RabbitMQ/Channel.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 9ed791c..f817d4e 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -430,9 +430,10 @@ sub publish { defined($header_args) or $header_args = {}; defined($body) or $body = ''; - defined($ack_cb) or defined($nack_cb) or defined($return_cb) - and !$self->{_is_confirm} - and croak "Can't set on_ack/on_nack/on_return callback when not in confirm mode"; + if ( defined($ack_cb) or defined($nack_cb) or defined($return_cb) ) { + croak "Can't set on_ack/on_nack/on_return callback when not in confirm mode" + unless $self->{_is_confirm}; + } my $tag; if ($self->{_is_confirm}) { From 7d5790b71f1a676bbf29a7a414cf1d5a927f6335 Mon Sep 17 00:00:00 2001 From: Nicolas R Date: Wed, 14 Mar 2018 16:06:20 -0500 Subject: [PATCH 096/165] fixes Makefile.PL for missing dot in INC dot was recently removed from @INC need to add it in order to load inc::Module::Install --- Makefile.PL | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile.PL b/Makefile.PL index ed155cf..d01818b 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,3 +1,5 @@ +BEGIN { unshift @INC, '.' } + use inc::Module::Install; name 'AnyEvent-RabbitMQ'; From 0db75649ebbed4ff74012f2a38f37a2e00638180 Mon Sep 17 00:00:00 2001 From: Scott O'Neil Date: Wed, 7 Mar 2018 16:07:41 -0600 Subject: [PATCH 097/165] Allow for SSL connection options to be passed to AnyEvent::Handle (cherry picked from commit 3e6e4a33cd0029e2abc9135cf537c5c9f1cbb4e6) Signed-off-by: Nicolas R --- lib/AnyEvent/RabbitMQ.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 03bfd3a..791ddb7 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -171,6 +171,7 @@ sub connect { if exists $self->{drain_condvar}; }, $args{tls} ? (tls => 'connect') : (), + $args{tls_ctx} ? ( tls_ctx => $args{tls_ctx} ) : (), ); $self->_read_loop($args{on_close}, $args{on_read_failure}); $self->_start(%args,); @@ -686,6 +687,7 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. vhost => '/', timeout => 1, tls => 0, # Or 1 if you'd like SSL + tls_ctx => $anyevent_tls # or a hash of AnyEvent::TLS options. tune => { heartbeat => 30, channel_max => $whatever, frame_max = $whatever }, on_success => sub { my $ar = shift; From 043a916f5079ddb3d4199dc0404cc01a4d285164 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 18:49:42 +0100 Subject: [PATCH 098/165] Add GitHub action to run tests Author tests against a RabbitMQ server not currently supported. --- .github/workflows/actions.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/actions.yml diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..669af4e --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,28 @@ +on: + push: + branches: + - master + tags: + - '*' +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] + perl: [ '5.30', '5.28', '5.10' ] + max-parallel: 2 + name: Perl ${{ matrix.perl }} on ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Set up perl + uses: shogo82148/actions-setup-perl@v1.3.1 + with: + perl-version: ${{ matrix.perl }} + - run: perl -V + - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Test::Pod Test::Spelling Test::Perl::Critic + - run: perl Makefile.PL + - run: cpanm --quiet --notest --installdeps . + - run: make + - run: make test + - run: prove -Iblib/lib -r xt/ From a6e75cea94289d596cc0c1ad213ba877d22b4a3c Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 20:50:22 +0100 Subject: [PATCH 099/165] We whitelist spelling for "canceled", so adjust Fixes xt/01_podspell.t --- lib/AnyEvent/RabbitMQ/Channel.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 9ed791c..7124f20 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -1216,7 +1216,7 @@ L, and C a L. =item on_cancel -Callback called if consumption is canceled. This may be at client request +Callback called if consumption is cancelled. This may be at client request or as a side effect of queue deletion. (Notification of queue deletion is a RabbitMQ extension.) From 2a63a3d4c2e885dcbb98193e98922acbffcecf71 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 20:51:32 +0100 Subject: [PATCH 100/165] TCP is a word --- xt/01_podspell.t | 1 + 1 file changed, 1 insertion(+) diff --git a/xt/01_podspell.t b/xt/01_podspell.t index 5d50bdd..b361d61 100644 --- a/xt/01_podspell.t +++ b/xt/01_podspell.t @@ -16,3 +16,4 @@ RabbitMQ multi ack qos +TCP From fb5a44be39b8e901c61eb1e94e74e21edc80279c Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 20:52:34 +0100 Subject: [PATCH 101/165] Bump changelog --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 0bd675e..1c48594 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Minor test fixes + 1.19 Sat Mar 21 16:49:24 GMT 2015 - Add 'no_ack' as an optional argument to the ->consume method (Dave Mueller). From 3e95e8db0e9a5f4981bccae5aa31c0a1c9d244a0 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 20:54:41 +0100 Subject: [PATCH 102/165] Run tests on all branches --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 669af4e..b6c2fd9 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -1,7 +1,7 @@ on: push: branches: - - master + - '*' tags: - '*' jobs: From b35f478cecf6766d1cbf1611af7ba8764b74cee2 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:06:11 +0100 Subject: [PATCH 103/165] Maintain changelog and credits --- .github/workflows/actions.yml | 2 +- Changes | 1 + Makefile.PL | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index b6c2fd9..d7adcda 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -20,7 +20,7 @@ jobs: with: perl-version: ${{ matrix.perl }} - run: perl -V - - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Test::Pod Test::Spelling Test::Perl::Critic + - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors Test::Pod Test::Spelling Test::Perl::Critic - run: perl Makefile.PL - run: cpanm --quiet --notest --installdeps . - run: make diff --git a/Changes b/Changes index 1c48594..0d3fd82 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Fix @INC breaking perls >= 5.26 (Nicolas R). - Minor test fixes 1.19 Sat Mar 21 16:49:24 GMT 2015 diff --git a/Makefile.PL b/Makefile.PL index d01818b..8cd5288 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -26,5 +26,9 @@ resources( repository => 'git://github.com/bobtfish/AnyEvent-RabbitMQ.git', ); +contributors 'Tom Doran '; +contributors 'Nicolas R '; +contributors 'Dave Lambley '; + WriteAll; From 4276a4f788b9f5ffb8065309b49d04756dfda278 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:11:38 +0100 Subject: [PATCH 104/165] Changelog and credits --- Changes | 1 + Makefile.PL | 1 + 2 files changed, 2 insertions(+) diff --git a/Changes b/Changes index 0d3fd82..54f802a 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Correct check when in confirm mode (Ruslan Zakirov). - Fix @INC breaking perls >= 5.26 (Nicolas R). - Minor test fixes diff --git a/Makefile.PL b/Makefile.PL index 8cd5288..886e4a0 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -29,6 +29,7 @@ resources( contributors 'Tom Doran '; contributors 'Nicolas R '; contributors 'Dave Lambley '; +contributors 'Ruslan Zakirov ' WriteAll; From ec52b93a388ba42e63a85b86f5f71804a6f161e3 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:15:22 +0100 Subject: [PATCH 105/165] Correct Makefile.PL credits --- Makefile.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.PL b/Makefile.PL index 886e4a0..9ecf948 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -29,7 +29,7 @@ resources( contributors 'Tom Doran '; contributors 'Nicolas R '; contributors 'Dave Lambley '; -contributors 'Ruslan Zakirov ' +contributors 'Ruslan Zakirov '; WriteAll; From 79e120f341c1f87d8e1c0cf107ca74d986dc94aa Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:16:40 +0100 Subject: [PATCH 106/165] Credits --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 54f802a..bacdf51 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Allow AnyEvent::TLS options to be passed (Nicolas R). - Correct check when in confirm mode (Ruslan Zakirov). - Fix @INC breaking perls >= 5.26 (Nicolas R). - Minor test fixes From ed66b1361fd840bc8e3f99a9129754819eb64093 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:19:10 +0100 Subject: [PATCH 107/165] Make development release --- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index bacdf51..0fc9887 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.19_01 Sat 16 May 21:18:27 BST 2020 - Allow AnyEvent::TLS options to be passed (Nicolas R). - Correct check when in confirm mode (Ruslan Zakirov). - Fix @INC breaking perls >= 5.26 (Nicolas R). diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 791ddb7..ea15621 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.19'; +our $VERSION = '1.19_01'; use constant { _ST_CLOSED => 0, From 29cf4286ee1174305e41fc98d3bc14e22ac33e60 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:24:09 +0100 Subject: [PATCH 108/165] Add original author --- Makefile.PL | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.PL b/Makefile.PL index 9ecf948..ac2490b 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -30,6 +30,7 @@ contributors 'Tom Doran '; contributors 'Nicolas R '; contributors 'Dave Lambley '; contributors 'Ruslan Zakirov '; +contributors 'cooldaemon '; WriteAll; From 9bd623884eaae00bb10fc4b21e3c4db7daab3839 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:29:52 +0100 Subject: [PATCH 109/165] Correct name --- Makefile.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.PL b/Makefile.PL index ac2490b..e40d961 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -30,7 +30,7 @@ contributors 'Tom Doran '; contributors 'Nicolas R '; contributors 'Dave Lambley '; contributors 'Ruslan Zakirov '; -contributors 'cooldaemon '; +contributors 'Masahito Ikuta '; WriteAll; From e6bca240bd486e5a070dea0f8267842761ded377 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 16 May 2020 21:36:27 +0100 Subject: [PATCH 110/165] XML::LibXML currently failing in Windows --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index d7adcda..85e0b7c 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] + os: ['ubuntu-latest', 'macos-latest'] perl: [ '5.30', '5.28', '5.10' ] max-parallel: 2 name: Perl ${{ matrix.perl }} on ${{ matrix.os }} From 1ee0cbdf66d79a600fdd4199154280afca2f62ec Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:09:58 +0100 Subject: [PATCH 111/165] Bring up RabbitMQ via https://github.community/t5/GitHub-Actions/Using-a-RabbitMQ-service-to-test-NPM-package/td-p/31885 --- .github/workflows/actions.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 85e0b7c..6915947 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -13,6 +13,14 @@ jobs: perl: [ '5.30', '5.28', '5.10' ] max-parallel: 2 name: Perl ${{ matrix.perl }} on ${{ matrix.os }} + + services: + rabbitmq: + image: rabbitmq:latest + ports: + - 5672/tcp + options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5 + steps: - uses: actions/checkout@v2 - name: Set up perl From c6a6b1caee7b92a38aeac8e37e33dc6d15ea3179 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:25:24 +0100 Subject: [PATCH 112/165] OSX doesn't run services, split tests --- .github/workflows/actions.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 6915947..1573830 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -5,11 +5,11 @@ on: tags: - '*' jobs: - build: + integration-test: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest'] + os: ['ubuntu-latest'] perl: [ '5.30', '5.28', '5.10' ] max-parallel: 2 name: Perl ${{ matrix.perl }} on ${{ matrix.os }} @@ -34,3 +34,24 @@ jobs: - run: make - run: make test - run: prove -Iblib/lib -r xt/ + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-latest', 'macos-latest'] + perl: [ '5.30', '5.28', '5.10' ] + max-parallel: 2 + name: Perl ${{ matrix.perl }} on ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Set up perl + uses: shogo82148/actions-setup-perl@v1.3.1 + with: + perl-version: ${{ matrix.perl }} + - run: perl -V + - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors + - run: perl Makefile.PL + - run: cpanm --quiet --notest --installdeps . + - run: make + - run: make test From 02b218aa1daa57ec8aa636379034ea8bfdd7ad74 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:29:13 +0100 Subject: [PATCH 113/165] Attempt to set vhost to match xt/*.t --- .github/workflows/actions.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 1573830..583d6c4 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -17,6 +17,8 @@ jobs: services: rabbitmq: image: rabbitmq:latest + env: + RABBITMQ_DEFAULT_VHOST: "/" ports: - 5672/tcp options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5 From 95a8dce46a928bd82e9319fdc0c9a59ef89c1e34 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:34:18 +0100 Subject: [PATCH 114/165] Map RabbitMQ port to host --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 583d6c4..e789e2e 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -20,7 +20,7 @@ jobs: env: RABBITMQ_DEFAULT_VHOST: "/" ports: - - 5672/tcp + - 5672:5672/tcp options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5 steps: From a2741574d7cb992e1a4cc459c16e29c37153165e Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:36:26 +0100 Subject: [PATCH 115/165] Try with a new Perl --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index e789e2e..bb3dd09 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest'] - perl: [ '5.30', '5.28', '5.10' ] + perl: [ '5.32', '5.30', '5.28', '5.10' ] max-parallel: 2 name: Perl ${{ matrix.perl }} on ${{ matrix.os }} From 3c9d7f6ffb04e902f85cd9bdaf50a12b8b0df9bd Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:40:29 +0100 Subject: [PATCH 116/165] Reduce error to a warning Error check was mostly broken prior to 6d6759c1d241a2274b64c28cd4cfdcd22e483633 so only warn to avoid breaking existing code. --- lib/AnyEvent/RabbitMQ/Channel.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 1ec8104..0029c88 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -7,7 +7,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use AnyEvent; use Scalar::Util qw( looks_like_number weaken ); use Devel::GlobalDestruction; -use Carp qw(croak); +use Carp qw(croak cluck); use POSIX qw(ceil); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } @@ -431,7 +431,7 @@ sub publish { defined($header_args) or $header_args = {}; defined($body) or $body = ''; if ( defined($ack_cb) or defined($nack_cb) or defined($return_cb) ) { - croak "Can't set on_ack/on_nack/on_return callback when not in confirm mode" + cluck "Can't set on_ack/on_nack/on_return callback when not in confirm mode" unless $self->{_is_confirm}; } From 670f97fbfd098db244c928602f148d97af4f9705 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:42:53 +0100 Subject: [PATCH 117/165] Revert "Try with a new Perl" This reverts commit a2741574d7cb992e1a4cc459c16e29c37153165e. 5.32 is not yet available in Docker --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index bb3dd09..e789e2e 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest'] - perl: [ '5.32', '5.30', '5.28', '5.10' ] + perl: [ '5.30', '5.28', '5.10' ] max-parallel: 2 name: Perl ${{ matrix.perl }} on ${{ matrix.os }} From eaaacd9070f138993fd6eb2e34dceea7ebee8e31 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:55:00 +0100 Subject: [PATCH 118/165] Produce CPAN tarball --- .github/workflows/actions.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index e789e2e..7ae0a56 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -12,7 +12,7 @@ jobs: os: ['ubuntu-latest'] perl: [ '5.30', '5.28', '5.10' ] max-parallel: 2 - name: Perl ${{ matrix.perl }} on ${{ matrix.os }} + name: Integration test ${{ matrix.perl }} on ${{ matrix.os }} services: rabbitmq: @@ -36,14 +36,14 @@ jobs: - run: make - run: make test - run: prove -Iblib/lib -r xt/ - build: + unit-test: runs-on: ${{ matrix.os }} strategy: matrix: os: ['ubuntu-latest', 'macos-latest'] perl: [ '5.30', '5.28', '5.10' ] max-parallel: 2 - name: Perl ${{ matrix.perl }} on ${{ matrix.os }} + name: Perl unit tests ${{ matrix.perl }} on ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -57,3 +57,26 @@ jobs: - run: cpanm --quiet --notest --installdeps . - run: make - run: make test + + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-latest'] + perl: [ '5.30' ] + max-parallel: 2 + name: CPAN build ${{ matrix.perl }} on ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Set up perl + uses: shogo82148/actions-setup-perl@v1.3.1 + with: + perl-version: ${{ matrix.perl }} + - run: perl -V + - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors + - run: perl Makefile.PL + - run: make tardist + - uses: actions/upload-artifact@v2 + with: + path: AnyEvent-RabbitMQ-*.tar.gz From db75d4fc21009babd48aa670ecdc648947ce6cc4 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 13:56:15 +0100 Subject: [PATCH 119/165] "make manifest" needed for tardist --- .github/workflows/actions.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 7ae0a56..771f46b 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -76,6 +76,7 @@ jobs: - run: perl -V - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors - run: perl Makefile.PL + - run: make manifest - run: make tardist - uses: actions/upload-artifact@v2 with: From 52281eef052424b18b8d1ff156b334baeb5439da Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 14:22:49 +0100 Subject: [PATCH 120/165] Release 1.20 --- Changes | 3 +++ lib/AnyEvent/RabbitMQ.pm | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 0fc9887..b2ce868 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.20 Sun 17 May 14:22:17 BST 2020 + - Downgrade error to a warning. + 1.19_01 Sat 16 May 21:18:27 BST 2020 - Allow AnyEvent::TLS options to be passed (Nicolas R). - Correct check when in confirm mode (Ruslan Zakirov). diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index ea15621..dbfcbbc 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.19_01'; +our $VERSION = '1.20'; use constant { _ST_CLOSED => 0, From 942af406cb6b2eb8aeab582534a8b80df9cefe70 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 17:15:41 +0100 Subject: [PATCH 121/165] Omit .github files from tarball --- MANIFEST.SKIP | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 4d3361c..88d8a0f 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -21,3 +21,4 @@ ^\.shipit$ ^\.gitignore$ ^\.travis.yml$ +^\.github$ From 7fcd21d78fbfbb3adc005ca9a145e8c574b96ae4 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 17:25:00 +0100 Subject: [PATCH 122/165] Perform testing using release tarball --- .github/workflows/actions.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 771f46b..332f170 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -6,6 +6,7 @@ on: - '*' jobs: integration-test: + needs: build runs-on: ${{ matrix.os }} strategy: matrix: @@ -24,19 +25,25 @@ jobs: options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v2 + - name: Grab release tarball + uses: actions/download-artifact@v1 + with: + name: release-tarball + - run: tar xzf release-tarball/AnyEvent-RabbitMQ-*.tar.gz --strip 1 + - run: rm -rf release-tarball - name: Set up perl uses: shogo82148/actions-setup-perl@v1.3.1 with: perl-version: ${{ matrix.perl }} - run: perl -V - - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors Test::Pod Test::Spelling Test::Perl::Critic - - run: perl Makefile.PL - run: cpanm --quiet --notest --installdeps . + - run: cpanm --quiet --notest Test::Pod Test::Spelling Test::Perl::Critic + - run: perl Makefile.PL - run: make - run: make test - run: prove -Iblib/lib -r xt/ unit-test: + needs: build runs-on: ${{ matrix.os }} strategy: matrix: @@ -46,15 +53,19 @@ jobs: name: Perl unit tests ${{ matrix.perl }} on ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - name: Grab release tarball + uses: actions/download-artifact@v1 + with: + name: release-tarball + - run: tar xzf release-tarball/AnyEvent-RabbitMQ-*.tar.gz --strip 1 + - run: rm -rf release-tarball - name: Set up perl uses: shogo82148/actions-setup-perl@v1.3.1 with: perl-version: ${{ matrix.perl }} - run: perl -V - - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors - - run: perl Makefile.PL - run: cpanm --quiet --notest --installdeps . + - run: perl Makefile.PL - run: make - run: make test @@ -81,3 +92,4 @@ jobs: - uses: actions/upload-artifact@v2 with: path: AnyEvent-RabbitMQ-*.tar.gz + name: release-tarball From 4c6fe9d6c7d4a148ef58ce3324b06d61c2fbf709 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 17:48:25 +0100 Subject: [PATCH 123/165] Omit subdirectories --- MANIFEST.SKIP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 88d8a0f..e922f2d 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -21,4 +21,4 @@ ^\.shipit$ ^\.gitignore$ ^\.travis.yml$ -^\.github$ +^\.github\/ From 172c0924186215eecb0aadd443bf23f9d8191da4 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 22:02:29 +0100 Subject: [PATCH 124/165] RT#88213 More docs for declare_queue Merge in documentation patch from rod.taylor@gmail.com https://rt.cpan.org/Ticket/Display.html?id=88213 --- Makefile.PL | 1 + lib/AnyEvent/RabbitMQ/Channel.pm | 38 +++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Makefile.PL b/Makefile.PL index e40d961..669bfdd 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -31,6 +31,7 @@ contributors 'Nicolas R '; contributors 'Dave Lambley '; contributors 'Ruslan Zakirov '; contributors 'Masahito Ikuta '; +contributors 'Rod Taylor '; WriteAll; diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 0029c88..50d4f63 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -1110,7 +1110,24 @@ The routing key to bind with =head2 declare_queue -Declare a queue, that is, create it if it doesn't exist yet. +Declare a queue (create it if it doesn't exist yet) for publishing messages +to on the server. + + my $done = AnyEvent->condvar; + $channel->declare_queue( + exchange => $queue_exchange, + queue => $queueName, + durable => 0, + auto_delete => 1, + passive => 0, + arguments => { 'x-expires' => 0, }, + on_success => sub { $done->send; }, + on_failure => sub { + say "Unable to create queue $queueName"; + $done->send; + }, + ); + $done->recv; Arguments: @@ -1161,6 +1178,25 @@ Callback that is called when the declaration of the queue has failed. default 0 +=for comment +XXX Is "exchange" a valid parameter? + +=item arguments + +C is a hashref of additional parameters which RabbitMQ extensions +may use. This list is not complete and your RabbitMQ server configuration will +determine which arguments are valid and how they act. + +=over + +=item x-expires + +The queue will automatically be removed after being idle for this many milliseconds. + +Default of 0 disables automatic queue removal. + +=back + =back =head2 bind_queue From 0587861bddf4d736dce77bf8602c5520f6dee3ed Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sun, 17 May 2020 22:07:42 +0100 Subject: [PATCH 125/165] Declare license --- .github/workflows/actions.yml | 2 +- Makefile.PL | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 332f170..c5c2eac 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -85,7 +85,7 @@ jobs: with: perl-version: ${{ matrix.perl }} - run: perl -V - - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors + - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors Module::Install::AutoLicense - run: perl Makefile.PL - run: make manifest - run: make tardist diff --git a/Makefile.PL b/Makefile.PL index 669bfdd..d97f03d 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -26,6 +26,9 @@ resources( repository => 'git://github.com/bobtfish/AnyEvent-RabbitMQ.git', ); +license 'perl'; +auto_license; + contributors 'Tom Doran '; contributors 'Nicolas R '; contributors 'Dave Lambley '; From 2e583e37461f379e808a053f89b2f3621947644c Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 18 May 2020 21:16:20 +0100 Subject: [PATCH 126/165] Bump changelog --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index b2ce868..79450c0 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Merge in documentation patch from + https://rt.cpan.org/Ticket/Display.html?id=88213 (Rod Taylor) + 1.20 Sun 17 May 14:22:17 BST 2020 - Downgrade error to a warning. From 791412784310af3fa7846f4127047c554288eecf Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 18 May 2020 21:30:27 +0100 Subject: [PATCH 127/165] Run tests on PR --- .github/workflows/actions.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index c5c2eac..20f152d 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -4,6 +4,13 @@ on: - '*' tags: - '*' + pull_request: + branches: + - master + types: + - opened + - reopened + jobs: integration-test: needs: build From 5dc27a1daf27b6bf7aea657fa555568691002f45 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 18 May 2020 21:37:56 +0100 Subject: [PATCH 128/165] Prevent channelMax overflowing https://rt.cpan.org/Ticket/Display.html?id=97716 --- Changes | 2 ++ Makefile.PL | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 79450c0..dfebc36 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Prevent channelMax from overflowing. + https://rt.cpan.org/Ticket/Display.html?id=97716 (Carl Hörberg) - Merge in documentation patch from https://rt.cpan.org/Ticket/Display.html?id=88213 (Rod Taylor) diff --git a/Makefile.PL b/Makefile.PL index d97f03d..342b296 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -35,6 +35,7 @@ contributors 'Dave Lambley '; contributors 'Ruslan Zakirov '; contributors 'Masahito Ikuta '; contributors 'Rod Taylor '; +contributors "Carl H\xf6rberg "; WriteAll; diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index dbfcbbc..574a72b 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -44,7 +44,7 @@ use constant { Readonly my $DEFAULT_AMQP_SPEC => File::ShareDir::dist_dir("AnyEvent-RabbitMQ") . '/fixed_amqp0-9-1.xml'; -Readonly my $DEFAULT_CHANNEL_MAX => 2**16; +Readonly my $DEFAULT_CHANNEL_MAX => 2**16-1; sub new { my $class = shift; From 96fc458424e5afa47003835b3d8b23b1567b4a12 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 19:36:54 +0100 Subject: [PATCH 129/165] Switch to Dist::Zilla --- .github/workflows/actions.yml | 10 ++++---- Makefile.PL | 41 -------------------------------- dist.ini | 44 +++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 47 deletions(-) delete mode 100644 Makefile.PL create mode 100644 dist.ini diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 20f152d..c909053 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -43,8 +43,7 @@ jobs: with: perl-version: ${{ matrix.perl }} - run: perl -V - - run: cpanm --quiet --notest --installdeps . - - run: cpanm --quiet --notest Test::Pod Test::Spelling Test::Perl::Critic + - run: cpanm --quiet --notest --installdeps --with-recommends . - run: perl Makefile.PL - run: make - run: make test @@ -92,10 +91,9 @@ jobs: with: perl-version: ${{ matrix.perl }} - run: perl -V - - run: cpanm --quiet --notest Module::Install Module::Install::AuthorTests Module::Install::AuthorRequires Module::Install::Contributors Module::Install::AutoLicense - - run: perl Makefile.PL - - run: make manifest - - run: make tardist + - run: cpanm --quiet --notest Dist::Zilla + - run: dzil authordeps | cpanm + - run: dzil build - uses: actions/upload-artifact@v2 with: path: AnyEvent-RabbitMQ-*.tar.gz diff --git a/Makefile.PL b/Makefile.PL deleted file mode 100644 index 342b296..0000000 --- a/Makefile.PL +++ /dev/null @@ -1,41 +0,0 @@ -BEGIN { unshift @INC, '.' } - -use inc::Module::Install; - -name 'AnyEvent-RabbitMQ'; -all_from 'lib/AnyEvent/RabbitMQ.pm'; - -requires 'List::MoreUtils'; -requires 'Net::AMQP' => '0.06'; -requires 'AnyEvent'; -requires 'Devel::GlobalDestruction'; -requires 'namespace::clean'; -requires 'File::ShareDir'; -requires 'Readonly' => '1.03'; - -tests 't/*.t'; -author_tests 'xt'; -install_share; - -perl_version '5.10'; -build_requires 'Test::More'; -build_requires 'Test::Exception'; -build_requires 'version'; - -resources( - repository => 'git://github.com/bobtfish/AnyEvent-RabbitMQ.git', -); - -license 'perl'; -auto_license; - -contributors 'Tom Doran '; -contributors 'Nicolas R '; -contributors 'Dave Lambley '; -contributors 'Ruslan Zakirov '; -contributors 'Masahito Ikuta '; -contributors 'Rod Taylor '; -contributors "Carl H\xf6rberg "; - -WriteAll; - diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..9a0a9dc --- /dev/null +++ b/dist.ini @@ -0,0 +1,44 @@ +name = AnyEvent-RabbitMQ +author = Masahito Ikuta +license = Perl_5 +copyright_holder = AnyEvent-RabbitMQ's developers + +[@Filter] +-bundle = @Basic +-remove = Readme + +[Metadata] +x_contributors = Tom Doran +x_contributors = Nicolas R +x_contributors = Dave Lambley +x_contributors = Ruslan Zakirov +x_contributors = Masahito Ikuta +x_contributors = Rod Taylor +x_contributors = Carl Hörberg + +[InstallGuide] +;[CPANFile] +[VersionFromModule] +[MetaJSON] + +[Repository] +repository = git://github.com/bobtfish/AnyEvent-RabbitMQ.git + +[Prereqs] +List::MoreUtils = 0 +Net::AMQP = 0.06 +AnyEvent = 0 +Devel::GlobalDestruction = 0 +namespace::clean = 0 +File::ShareDir = 0 +Readonly = 1.03 + +[Prereqs / TestRequires] +Test::More = 0 +Test::Exception = 0 +version = 0 + +[Prereqs / TestRecommends] +Test::Spelling = 0 +Test::Perl::Critic = 0 +Test::Pod = 0 From 4fc01c9f5b4b22f1e1840c4b9f21729a01f85d07 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 19:45:30 +0100 Subject: [PATCH 130/165] Keep cpanm quiet --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index c909053..f31bcf9 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -92,7 +92,7 @@ jobs: perl-version: ${{ matrix.perl }} - run: perl -V - run: cpanm --quiet --notest Dist::Zilla - - run: dzil authordeps | cpanm + - run: dzil authordeps | cpanm --quiet - run: dzil build - uses: actions/upload-artifact@v2 with: From 5ff09775882b6e9523f4b6887b80e42be0f4a087 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 20:48:36 +0100 Subject: [PATCH 131/165] Restore perl dependency Lost in 96fc458424e5afa47003835b3d8b23b1567b4a12 --- dist.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/dist.ini b/dist.ini index 9a0a9dc..39c7aad 100644 --- a/dist.ini +++ b/dist.ini @@ -32,6 +32,7 @@ Devel::GlobalDestruction = 0 namespace::clean = 0 File::ShareDir = 0 Readonly = 1.03 +perl = 5.010 [Prereqs / TestRequires] Test::More = 0 From 3a5abe890357e0461200077d57f0d81c7e7acc18 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 20:49:50 +0100 Subject: [PATCH 132/165] cpanfile should be enabled --- dist.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist.ini b/dist.ini index 39c7aad..beed694 100644 --- a/dist.ini +++ b/dist.ini @@ -17,7 +17,7 @@ x_contributors = Rod Taylor x_contributors = Carl Hörberg [InstallGuide] -;[CPANFile] +[CPANFile] [VersionFromModule] [MetaJSON] From 8629a238be59cf618bc16f7ac782de395d130a6d Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 20:59:24 +0100 Subject: [PATCH 133/165] MetaResources is Dist::Zilla core and preferred --- dist.ini | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dist.ini b/dist.ini index beed694..c9e9d5b 100644 --- a/dist.ini +++ b/dist.ini @@ -21,8 +21,10 @@ x_contributors = Carl Hörberg [VersionFromModule] [MetaJSON] -[Repository] -repository = git://github.com/bobtfish/AnyEvent-RabbitMQ.git +[MetaResources] +repository.url = git://github.com/bobtfish/AnyEvent-RabbitMQ.git +repository.web = https://github.com/bobtfish/AnyEvent-RabbitMQ +repository.type = git [Prereqs] List::MoreUtils = 0 From 01d5fc1611e975688fdc258f9f104c900512a097 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 22:54:39 +0100 Subject: [PATCH 134/165] Do spellcheck with [Test::PodSpelling] --- dist.ini | 6 ++++++ xt/01_podspell.t | 19 ------------------- 2 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 xt/01_podspell.t diff --git a/dist.ini b/dist.ini index c9e9d5b..3a17ac5 100644 --- a/dist.ini +++ b/dist.ini @@ -26,6 +26,12 @@ repository.url = git://github.com/bobtfish/AnyEvent-RabbitMQ.git repository.web = https://github.com/bobtfish/AnyEvent-RabbitMQ repository.type = git +[Test::PodSpelling] +stopword = TCP +stopword = ack +stopword = qos +stopword = AMQP + [Prereqs] List::MoreUtils = 0 Net::AMQP = 0.06 diff --git a/xt/01_podspell.t b/xt/01_podspell.t deleted file mode 100644 index b361d61..0000000 --- a/xt/01_podspell.t +++ /dev/null @@ -1,19 +0,0 @@ -use Test::More; -eval q{ use Test::Spelling }; -plan skip_all => "Test::Spelling is not installed." if $@; -add_stopwords(map { split /[\s\:\-]/ } ); -set_spell_cmd('aspell list'); -$ENV{LANG} = 'C'; -all_pod_files_spelling_ok('lib'); -__DATA__ -API -signalled -cancelled -Masahito Ikuta -cooldaemon@gmail.com -AMQP -RabbitMQ -multi -ack -qos -TCP From c6b880a5be8001dd9fc8b9df006e873bd945fd63 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 22:58:07 +0100 Subject: [PATCH 135/165] Use [Test::Perl::Critic] for perlcritic --- dist.ini | 2 ++ xt/perlcriticrc => perlcritic.rc | 0 xt/02_perlcritic.t | 8 -------- 3 files changed, 2 insertions(+), 8 deletions(-) rename xt/perlcriticrc => perlcritic.rc (100%) delete mode 100644 xt/02_perlcritic.t diff --git a/dist.ini b/dist.ini index 3a17ac5..5891c30 100644 --- a/dist.ini +++ b/dist.ini @@ -32,6 +32,8 @@ stopword = ack stopword = qos stopword = AMQP +[Test::Perl::Critic] + [Prereqs] List::MoreUtils = 0 Net::AMQP = 0.06 diff --git a/xt/perlcriticrc b/perlcritic.rc similarity index 100% rename from xt/perlcriticrc rename to perlcritic.rc diff --git a/xt/02_perlcritic.t b/xt/02_perlcritic.t deleted file mode 100644 index b977df8..0000000 --- a/xt/02_perlcritic.t +++ /dev/null @@ -1,8 +0,0 @@ -use strict; -use Test::More; -eval { - require Test::Perl::Critic; - Test::Perl::Critic->import( -profile => 'xt/perlcriticrc'); -}; -plan skip_all => "Test::Perl::Critic is not installed." if $@; -all_critic_ok('lib'); From 1c00d8a88b8a88916422d8f4e110a3963c83117d Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 23:00:12 +0100 Subject: [PATCH 136/165] No longer needed --- dist.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/dist.ini b/dist.ini index 5891c30..470b1ab 100644 --- a/dist.ini +++ b/dist.ini @@ -50,6 +50,4 @@ Test::Exception = 0 version = 0 [Prereqs / TestRecommends] -Test::Spelling = 0 -Test::Perl::Critic = 0 Test::Pod = 0 From a6cb12b9c53832ef2f8cbacc4a38e57d31f180f1 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 23:02:23 +0100 Subject: [PATCH 137/165] Document thing I don't want to change now --- xt/03_pod.t | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xt/03_pod.t b/xt/03_pod.t index 437887a..b4bccc9 100644 --- a/xt/03_pod.t +++ b/xt/03_pod.t @@ -1,3 +1,6 @@ +# XXX should be replaced by Dist::Zilla::Plugin::PodSyntaxTests, but holding +# of for eas of development on Ubuntu 18.04. + use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; From 6bf5fecb22fb9d87617758db9b09f113efa701fb Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 23:10:52 +0100 Subject: [PATCH 138/165] Enable authoring testing on integration test --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index f31bcf9..d95fa86 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -46,7 +46,7 @@ jobs: - run: cpanm --quiet --notest --installdeps --with-recommends . - run: perl Makefile.PL - run: make - - run: make test + - run: AUTHOR_TESTING=1 make test - run: prove -Iblib/lib -r xt/ unit-test: needs: build From 495954130302459db563b7d8826d1367ef96b9d8 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Tue, 19 May 2020 23:23:41 +0100 Subject: [PATCH 139/165] Author test deps now in the develop phase --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index d95fa86..ffa9158 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -43,7 +43,7 @@ jobs: with: perl-version: ${{ matrix.perl }} - run: perl -V - - run: cpanm --quiet --notest --installdeps --with-recommends . + - run: cpanm --quiet --notest --installdeps --with-recommends --with-develop . - run: perl Makefile.PL - run: make - run: AUTHOR_TESTING=1 make test From 80bdeb3d8702ad591f0b8ae287c7d987ce15cf02 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 20 May 2020 21:55:54 +0100 Subject: [PATCH 140/165] Correct typos in comment --- xt/03_pod.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xt/03_pod.t b/xt/03_pod.t index b4bccc9..2c01bec 100644 --- a/xt/03_pod.t +++ b/xt/03_pod.t @@ -1,5 +1,5 @@ -# XXX should be replaced by Dist::Zilla::Plugin::PodSyntaxTests, but holding -# of for eas of development on Ubuntu 18.04. +# TODO should be replaced by Dist::Zilla::Plugin::PodSyntaxTests, but holding +# off for ease of development on Ubuntu 18.04. use Test::More; eval "use Test::Pod 1.00"; From 574935906bc7d851058322719f08a662b9a18b2d Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Fri, 22 May 2020 18:58:22 +0100 Subject: [PATCH 141/165] RT#106550 Add no_ack param to get() Merge in patch from Julio Polo https://rt.cpan.org/Ticket/Display.html?id=106550 --- Changes | 1 + lib/AnyEvent/RabbitMQ/Channel.pm | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index dfebc36..caa66c3 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Add "no_ack" flag to "get" method. (Julio Polo) - Prevent channelMax from overflowing. https://rt.cpan.org/Ticket/Display.html?id=97716 (Carl Hörberg) - Merge in documentation patch from diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 50d4f63..4edd382 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -617,12 +617,14 @@ sub get { my $self = shift; my ($cb, $failure_cb, %args) = $self->_delete_cbs(@_); + my $no_ack = delete $args{no_ack} // 1; + return $self if !$self->_check_open($failure_cb); $self->{connection}->_push_write_and_read( 'Basic::Get', { - no_ack => 1, + no_ack => $no_ack, %args, # queue ticket => 0, }, @@ -1385,6 +1387,10 @@ a notification that there was nothing to collect from the queue. This callback will be called if an error is signalled on this channel. +=item no_ack + +0 or 1, default 1 + =back =head2 ack From cf79ad745f4ae6cb98f25a0b7614e9ee55e6c947 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Fri, 22 May 2020 19:06:35 +0100 Subject: [PATCH 142/165] Attempt simpler list from example https://help.github.com/en/actions/reference/events-that-trigger-workflows#example-using-a-list-of-events --- .github/workflows/actions.yml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 20f152d..c849075 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -1,15 +1,4 @@ -on: - push: - branches: - - '*' - tags: - - '*' - pull_request: - branches: - - master - types: - - opened - - reopened +on: [push, pull_request] jobs: integration-test: From 53f912816e3e09c37f5cc1be9a17b5fe5be43f15 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 23 May 2020 12:41:55 +0100 Subject: [PATCH 143/165] Add contributor --- dist.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/dist.ini b/dist.ini index 470b1ab..5e6d7dc 100644 --- a/dist.ini +++ b/dist.ini @@ -15,6 +15,7 @@ x_contributors = Ruslan Zakirov x_contributors = Masahito Ikuta x_contributors = Rod Taylor x_contributors = Carl Hörberg +x_contributors = Julio Polo [InstallGuide] [CPANFile] From be1c1185ea4a1146c1f8e81623bb1246a61aba5f Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 23 May 2020 23:30:49 +0100 Subject: [PATCH 144/165] Bump release --- Changes | 1 + lib/AnyEvent/RabbitMQ.pm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index caa66c3..fe0a83b 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.20_01 Sat 23 May 23:29:50 BST 2020 - Add "no_ack" flag to "get" method. (Julio Polo) - Prevent channelMax from overflowing. https://rt.cpan.org/Ticket/Display.html?id=97716 (Carl Hörberg) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 574a72b..b4e465c 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.20'; +our $VERSION = '1.20_01'; use constant { _ST_CLOSED => 0, From 9e7356c36615c2e05f670779c797740c6a11900f Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Sat, 30 May 2020 23:42:50 +0100 Subject: [PATCH 145/165] Bump version --- Changes | 3 +++ lib/AnyEvent/RabbitMQ.pm | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index fe0a83b..02817c6 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.21 Sat 30 May 23:40:54 BST 2020 + - No changes. + 1.20_01 Sat 23 May 23:29:50 BST 2020 - Add "no_ack" flag to "get" method. (Julio Polo) - Prevent channelMax from overflowing. diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index b4e465c..af88d0c 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.20_01'; +our $VERSION = '1.21'; use constant { _ST_CLOSED => 0, From 6d7f64e6a750b9fa339793cad90d1e5c716048cd Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 3 Jun 2020 22:08:59 +0100 Subject: [PATCH 146/165] Use Dist::Zilla::Plugin::OurPkgVersion Kwalitee complains that the non main modules have regressed in version, as they no version number. --- dist.ini | 5 ++++- lib/AnyEvent/RabbitMQ.pm | 2 +- lib/AnyEvent/RabbitMQ/Channel.pm | 2 ++ lib/AnyEvent/RabbitMQ/LocalQueue.pm | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/dist.ini b/dist.ini index 5e6d7dc..c2781d8 100644 --- a/dist.ini +++ b/dist.ini @@ -2,6 +2,7 @@ name = AnyEvent-RabbitMQ author = Masahito Ikuta license = Perl_5 copyright_holder = AnyEvent-RabbitMQ's developers +version = 1.21_02 [@Filter] -bundle = @Basic @@ -19,9 +20,11 @@ x_contributors = Julio Polo [InstallGuide] [CPANFile] -[VersionFromModule] [MetaJSON] +[OurPkgVersion] +underscore_eval_version=1 + [MetaResources] repository.url = git://github.com/bobtfish/AnyEvent-RabbitMQ.git repository.web = https://github.com/bobtfish/AnyEvent-RabbitMQ diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index af88d0c..06fe61a 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -32,7 +32,7 @@ use AnyEvent::RabbitMQ::LocalQueue; use namespace::clean; -our $VERSION = '1.21'; +# VERSION use constant { _ST_CLOSED => 0, diff --git a/lib/AnyEvent/RabbitMQ/Channel.pm b/lib/AnyEvent/RabbitMQ/Channel.pm index 4edd382..ab19ef1 100644 --- a/lib/AnyEvent/RabbitMQ/Channel.pm +++ b/lib/AnyEvent/RabbitMQ/Channel.pm @@ -11,6 +11,8 @@ use Carp qw(croak cluck); use POSIX qw(ceil); BEGIN { *Dumper = \&AnyEvent::RabbitMQ::Dumper } +# VERSION + use namespace::clean; use constant { diff --git a/lib/AnyEvent/RabbitMQ/LocalQueue.pm b/lib/AnyEvent/RabbitMQ/LocalQueue.pm index de5423f..6e222a8 100644 --- a/lib/AnyEvent/RabbitMQ/LocalQueue.pm +++ b/lib/AnyEvent/RabbitMQ/LocalQueue.pm @@ -3,6 +3,8 @@ package AnyEvent::RabbitMQ::LocalQueue; use strict; use warnings; +# VERSION + sub new { my $class = shift; return bless { From cb47a14746772d1d4cf9909f10bd643e44fc8642 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 3 Jun 2020 22:10:14 +0100 Subject: [PATCH 147/165] Bump changelog --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 02817c6..09b5406 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Assign a version number to all modules in package. + 1.21 Sat 30 May 23:40:54 BST 2020 - No changes. From bbd795bc036cee632b6bfb6c2bc2cbb9a48482bc Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 3 Jun 2020 22:31:18 +0100 Subject: [PATCH 148/165] Keep perlcritic happy --- dist.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dist.ini b/dist.ini index c2781d8..e9b4109 100644 --- a/dist.ini +++ b/dist.ini @@ -23,7 +23,8 @@ x_contributors = Julio Polo [MetaJSON] [OurPkgVersion] -underscore_eval_version=1 +underscore_eval_version = 1 +no_critic = 1 [MetaResources] repository.url = git://github.com/bobtfish/AnyEvent-RabbitMQ.git From d2a6a9a37cb2bb07aa19e8a2b4d816ebac4982e7 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 3 Jun 2020 22:32:29 +0100 Subject: [PATCH 149/165] Don't run tests on authordeps Be consistent with other use of cpanm --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 8aeb8c1..32c190c 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -81,7 +81,7 @@ jobs: perl-version: ${{ matrix.perl }} - run: perl -V - run: cpanm --quiet --notest Dist::Zilla - - run: dzil authordeps | cpanm --quiet + - run: dzil authordeps | cpanm --quiet -notest - run: dzil build - uses: actions/upload-artifact@v2 with: From 12f85bf55f342bdecf4ec3024e02302b0163a331 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 8 Jun 2020 21:09:42 +0100 Subject: [PATCH 150/165] RT#119793 Add "nodelay" option https://github.com/cooldaemon/AnyEvent-RabbitMQ/pull/5/commits/e888205306fb49167c0dc9aa4e4dc940cc922d25 https://rt.cpan.org/Ticket/Display.html?id=119793 --- Changes | 4 ++++ dist.ini | 2 ++ lib/AnyEvent/RabbitMQ.pm | 2 ++ xt/04_anyevent.t | 29 +++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/Changes b/Changes index 02817c6..faac688 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Add "nodelay" option to disable Nagle's algorithm. + https://rt.cpan.org/Ticket/Display.html?id=119793 + Also José Micó's e888205306fb49167c0dc9aa4e4dc940cc922d25. + 1.21 Sat 30 May 23:40:54 BST 2020 - No changes. diff --git a/dist.ini b/dist.ini index 5e6d7dc..2d41b04 100644 --- a/dist.ini +++ b/dist.ini @@ -16,6 +16,8 @@ x_contributors = Masahito Ikuta x_contributors = Rod Taylor x_contributors = Carl Hörberg x_contributors = Julio Polo +x_contributors = A.J. Ragusa +x_contributors = José Micó [InstallGuide] [CPANFile] diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index af88d0c..2c501b4 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -172,6 +172,7 @@ sub connect { }, $args{tls} ? (tls => 'connect') : (), $args{tls_ctx} ? ( tls_ctx => $args{tls_ctx} ) : (), + $args{nodelay} ? ( nodelay => $args{nodelay} ) : (), ); $self->_read_loop($args{on_close}, $args{on_read_failure}); $self->_start(%args,); @@ -689,6 +690,7 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. tls => 0, # Or 1 if you'd like SSL tls_ctx => $anyevent_tls # or a hash of AnyEvent::TLS options. tune => { heartbeat => 30, channel_max => $whatever, frame_max = $whatever }, + nodelay => 1, # Reduces latency by disabling Nagle's algorithm on_success => sub { my $ar = shift; $ar->open_channel( diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index 9cc0289..fc7b0e9 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -42,6 +42,35 @@ lives_ok sub { $ar->load_xml_spec() }, 'load xml spec'; +my @nagle = [[], [nodelay => 0], [nodelay => 1]]; + +for my $opt (@nagle) { + my $done = AnyEvent->condvar; + my $z = AnyEvent::RabbitMQ->new(verbose => $conf{verbose}); + $z->connect( + (map {$_ => $conf{$_}} qw(host port user pass vhost)), + timeout => 1, + on_success => sub { + my $ar = shift; + isa_ok($ar, 'AnyEvent::RabbitMQ'); + $done->send; + }, + on_failure => failure_cb($done), + on_return => sub { + my $method_frame = shift->method_frame; + die "return: ", $method_frame->reply_code, $method_frame->reply_text + if $method_frame->reply_code; + }, + on_close => sub { + my $method_frame = shift->method_frame; + Carp::confess "close: ", $method_frame->reply_code, $method_frame->reply_text + if $method_frame->reply_code; + }, + @{ $opt }, + ); + $done->recv; +} + my $done = AnyEvent->condvar; $ar->connect( (map {$_ => $conf{$_}} qw(host port user pass vhost)), From 2511c87a67908f1ebc5ccc485bc6450de4952576 Mon Sep 17 00:00:00 2001 From: Scott O'Neil Date: Sun, 17 May 2020 01:23:25 +0000 Subject: [PATCH 151/165] Pass peername to AnyEvent::Handle so that TLS peer validation is possible --- lib/AnyEvent/RabbitMQ.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 06fe61a..81a3ebd 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -170,6 +170,7 @@ sub connect { $self->{drain_condvar}->send if exists $self->{drain_condvar}; }, + peername => $args{host}, $args{tls} ? (tls => 'connect') : (), $args{tls_ctx} ? ( tls_ctx => $args{tls_ctx} ) : (), ); From c4eb4426c11cf9efb6731b73fed4e87c3c75b24d Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 10 Jun 2020 12:20:43 +0100 Subject: [PATCH 152/165] Bump changelog --- Changes | 1 + dist.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/Changes b/Changes index ddf4112..0ed223a 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Validate peer when using TLS. (Scott O'Neil) - Add "nodelay" option to disable Nagle's algorithm. https://rt.cpan.org/Ticket/Display.html?id=119793 Also José Micó's e888205306fb49167c0dc9aa4e4dc940cc922d25. diff --git a/dist.ini b/dist.ini index 297915f..979be00 100644 --- a/dist.ini +++ b/dist.ini @@ -19,6 +19,7 @@ x_contributors = Carl Hörberg x_contributors = Julio Polo x_contributors = A.J. Ragusa x_contributors = José Micó +x_contributors = Scott O'Neil [InstallGuide] [CPANFile] From 2ecc3c62244e9a0b53a9b32a5f2c4929d9c94ada Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 10 Jun 2020 12:27:29 +0100 Subject: [PATCH 153/165] Bump version for release --- Changes | 3 ++- dist.ini | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 0ed223a..e431dee 100644 --- a/Changes +++ b/Changes @@ -1,12 +1,13 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.21_01 Wed 10 Jun 12:23:48 BST 2020 - Validate peer when using TLS. (Scott O'Neil) - Add "nodelay" option to disable Nagle's algorithm. https://rt.cpan.org/Ticket/Display.html?id=119793 Also José Micó's e888205306fb49167c0dc9aa4e4dc940cc922d25. - Assign a version number to all modules in package. -1.21 Sat 30 May 23:40:54 BST 2020 +1.21 Sat 30 May 23:40:54 BST 2020 - No changes. 1.20_01 Sat 23 May 23:29:50 BST 2020 diff --git a/dist.ini b/dist.ini index 979be00..2a23aa1 100644 --- a/dist.ini +++ b/dist.ini @@ -2,7 +2,7 @@ name = AnyEvent-RabbitMQ author = Masahito Ikuta license = Perl_5 copyright_holder = AnyEvent-RabbitMQ's developers -version = 1.21_02 +version = 1.21_01 [@Filter] -bundle = @Basic From f00992955c3b8569772acfca083ec622bbbdbfca Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 10 Jun 2020 12:46:55 +0100 Subject: [PATCH 154/165] We claim to work in 5.10.0. --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 32c190c..62df7ca 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest'] - perl: [ '5.30', '5.28', '5.10' ] + perl: [ '5.30', '5.28', '5.10', '5.10.0' ] max-parallel: 2 name: Integration test ${{ matrix.perl }} on ${{ matrix.os }} From 0e776b8f9346e6ca3444cebed1740bd9aaf1977e Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 10 Jun 2020 14:46:10 +0100 Subject: [PATCH 155/165] Demand version --- dist.ini | 2 +- xt/04_anyevent.t | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dist.ini b/dist.ini index 2a23aa1..f881ba7 100644 --- a/dist.ini +++ b/dist.ini @@ -55,7 +55,7 @@ perl = 5.010 [Prereqs / TestRequires] Test::More = 0 Test::Exception = 0 -version = 0 +version = 0.77 [Prereqs / TestRecommends] Test::Pod = 0 diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index fc7b0e9..80f6093 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -1,6 +1,7 @@ use Test::More; use Test::Exception; use Data::Dumper; +use version 0.77; use FindBin; From 7c178a71b8e822be68b156558bd2aeab573e3e9f Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Wed, 10 Jun 2020 19:12:59 +0100 Subject: [PATCH 156/165] Correct array --- xt/04_anyevent.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xt/04_anyevent.t b/xt/04_anyevent.t index 80f6093..fc4be68 100644 --- a/xt/04_anyevent.t +++ b/xt/04_anyevent.t @@ -43,7 +43,7 @@ lives_ok sub { $ar->load_xml_spec() }, 'load xml spec'; -my @nagle = [[], [nodelay => 0], [nodelay => 1]]; +my @nagle = ([], [nodelay => 0], [nodelay => 1]); for my $opt (@nagle) { my $done = AnyEvent->condvar; From 9dc30dbb3c9cbefc5295088e460c409ef604af30 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Fri, 12 Jun 2020 19:47:08 +0100 Subject: [PATCH 157/165] Use correct parameter for cpanm --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 62df7ca..3b161d6 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -81,7 +81,7 @@ jobs: perl-version: ${{ matrix.perl }} - run: perl -V - run: cpanm --quiet --notest Dist::Zilla - - run: dzil authordeps | cpanm --quiet -notest + - run: dzil authordeps | cpanm --quiet --notest - run: dzil build - uses: actions/upload-artifact@v2 with: From 5e1c388f45de58153d8cd6e68e5565e5fe5a292f Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 8 Jun 2020 21:19:24 +0100 Subject: [PATCH 158/165] Use Strawberry Perl to test under Windows Ordinary Perl runs into trouble with Alien::LibXML failing under Windows. Strawberry Perl has libxml already installed. Also avoid shell globbing, as that doesn't work under Windows (at least not without bash.) Upgrades actions-setup-perl to v1.4.0 too. --- .github/workflows/actions.yml | 41 ++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 3b161d6..4793861 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -25,10 +25,10 @@ jobs: uses: actions/download-artifact@v1 with: name: release-tarball - - run: tar xzf release-tarball/AnyEvent-RabbitMQ-*.tar.gz --strip 1 + - run: tar xzf release-tarball/ar.tar.gz --strip 1 - run: rm -rf release-tarball - name: Set up perl - uses: shogo82148/actions-setup-perl@v1.3.1 + uses: shogo82148/actions-setup-perl@v1.4.0 with: perl-version: ${{ matrix.perl }} - run: perl -V @@ -44,7 +44,7 @@ jobs: matrix: os: ['ubuntu-latest', 'macos-latest'] perl: [ '5.30', '5.28', '5.10' ] - max-parallel: 2 + max-parallel: 3 name: Perl unit tests ${{ matrix.perl }} on ${{ matrix.os }} steps: @@ -52,10 +52,9 @@ jobs: uses: actions/download-artifact@v1 with: name: release-tarball - - run: tar xzf release-tarball/AnyEvent-RabbitMQ-*.tar.gz --strip 1 - - run: rm -rf release-tarball + - run: tar xzf release-tarball/ar.tar.gz --strip 1 - name: Set up perl - uses: shogo82148/actions-setup-perl@v1.3.1 + uses: shogo82148/actions-setup-perl@v1.4.0 with: perl-version: ${{ matrix.perl }} - run: perl -V @@ -64,6 +63,31 @@ jobs: - run: make - run: make test + strawberry-unit-test: + needs: build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['windows-latest'] + perl: [ '5.30.2.1', '5.28.2.1' ] + arch: ['64bit'] + max-parallel: 3 + name: Strawberry Perl unit tests on ${{ matrix.os }} with ${{ matrix.arch }} perl ${{ matrix.perl }} + + steps: + - name: Grab release tarball + uses: actions/download-artifact@v1 + with: + name: release-tarball + - run: tar xzf release-tarball/ar.tar.gz --strip 1 + - run: Invoke-WebRequest -Uri http://strawberryperl.com/download/${{ matrix.perl }}/strawberry-perl-${{ matrix.perl }}-${{ matrix.arch }}.msi -OutFile c:\strawberry-perl-${{ matrix.perl }}-${{ matrix.arch }}.msi + - run: Start-Process msiexec.exe -Wait -ArgumentList '/I C:\strawberry-perl-${{ matrix.perl }}-${{ matrix.arch }}.msi /quiet' + - run: perl -V + - run: cpanm --quiet --notest --installdeps . + - run: perl Makefile.PL + - run: make + - run: make test + build: runs-on: ${{ matrix.os }} strategy: @@ -76,14 +100,15 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up perl - uses: shogo82148/actions-setup-perl@v1.3.1 + uses: shogo82148/actions-setup-perl@v1.4.0 with: perl-version: ${{ matrix.perl }} - run: perl -V - run: cpanm --quiet --notest Dist::Zilla - run: dzil authordeps | cpanm --quiet --notest - run: dzil build + - run: mv AnyEvent-RabbitMQ-*.tar.gz ar.tar.gz - uses: actions/upload-artifact@v2 with: - path: AnyEvent-RabbitMQ-*.tar.gz + path: ar.tar.gz name: release-tarball From 134899ae1c6abc834f6a5aecc0474fbab1b1a5d7 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Fri, 12 Jun 2020 19:49:28 +0100 Subject: [PATCH 159/165] Bump changelog --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index e431dee..c4d9854 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ + - Minor correction to author tests. + 1.21_01 Wed 10 Jun 12:23:48 BST 2020 - Validate peer when using TLS. (Scott O'Neil) - Add "nodelay" option to disable Nagle's algorithm. From 03d14b16aad1b5b467a680f9059694b0b872885c Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Fri, 12 Jun 2020 19:56:31 +0100 Subject: [PATCH 160/165] Release version 1.22 --- Changes | 1 + dist.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index c4d9854..23e61e4 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Revision history for Perl extension AnyEvent::RabbitMQ +1.22 Fri 12 Jun 19:55:03 BST 2020 - Minor correction to author tests. 1.21_01 Wed 10 Jun 12:23:48 BST 2020 diff --git a/dist.ini b/dist.ini index f881ba7..f67b9dc 100644 --- a/dist.ini +++ b/dist.ini @@ -2,7 +2,7 @@ name = AnyEvent-RabbitMQ author = Masahito Ikuta license = Perl_5 copyright_holder = AnyEvent-RabbitMQ's developers -version = 1.21_01 +version = 1.22 [@Filter] -bundle = @Basic From 3de574ddd5614c143fe092a44f7348629cfebf21 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Fri, 12 Jun 2020 19:58:16 +0100 Subject: [PATCH 161/165] More changelog --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 23e61e4..d2128db 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,7 @@ Revision history for Perl extension AnyEvent::RabbitMQ 1.22 Fri 12 Jun 19:55:03 BST 2020 - Minor correction to author tests. + - Correct version constraint on version.pm. 1.21_01 Wed 10 Jun 12:23:48 BST 2020 - Validate peer when using TLS. (Scott O'Neil) From 3ec7d22d261cf19ad0aaecd5818cb77c4e5c238e Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 23 May 2022 19:54:05 +0100 Subject: [PATCH 162/165] RT#142878 Correct errors in synopsis Fix by Eugen Konkov. --- dist.ini | 1 + lib/AnyEvent/RabbitMQ.pm | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dist.ini b/dist.ini index f67b9dc..61375ed 100644 --- a/dist.ini +++ b/dist.ini @@ -20,6 +20,7 @@ x_contributors = Julio Polo x_contributors = A.J. Ragusa x_contributors = José Micó x_contributors = Scott O'Neil +x_contributors = Eugen Konkov [InstallGuide] [CPANFile] diff --git a/lib/AnyEvent/RabbitMQ.pm b/lib/AnyEvent/RabbitMQ.pm index 3488045..98bcc8b 100644 --- a/lib/AnyEvent/RabbitMQ.pm +++ b/lib/AnyEvent/RabbitMQ.pm @@ -689,8 +689,8 @@ AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client. vhost => '/', timeout => 1, tls => 0, # Or 1 if you'd like SSL - tls_ctx => $anyevent_tls # or a hash of AnyEvent::TLS options. - tune => { heartbeat => 30, channel_max => $whatever, frame_max = $whatever }, + tls_ctx => $anyevent_tls, # or a hash of AnyEvent::TLS options. + tune => { heartbeat => 30, channel_max => $whatever, frame_max => $whatever }, nodelay => 1, # Reduces latency by disabling Nagle's algorithm on_success => sub { my $ar = shift; From 54e6c05759051f5be70b72082c5e0f82f9f31949 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 23 May 2022 19:56:39 +0100 Subject: [PATCH 163/165] Add email address for CPAN ID --- dist.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist.ini b/dist.ini index 61375ed..08325f6 100644 --- a/dist.ini +++ b/dist.ini @@ -18,7 +18,7 @@ x_contributors = Rod Taylor x_contributors = Carl Hörberg x_contributors = Julio Polo x_contributors = A.J. Ragusa -x_contributors = José Micó +x_contributors = José Micó x_contributors = Scott O'Neil x_contributors = Eugen Konkov From dfa31927cadd6546853c7ad7517c73548597f06a Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 23 May 2022 20:26:41 +0100 Subject: [PATCH 164/165] Old version broken, try current --- .github/workflows/actions.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 4793861..6bdba86 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -28,7 +28,7 @@ jobs: - run: tar xzf release-tarball/ar.tar.gz --strip 1 - run: rm -rf release-tarball - name: Set up perl - uses: shogo82148/actions-setup-perl@v1.4.0 + uses: shogo82148/actions-setup-perl@v1.15.3 with: perl-version: ${{ matrix.perl }} - run: perl -V @@ -54,7 +54,7 @@ jobs: name: release-tarball - run: tar xzf release-tarball/ar.tar.gz --strip 1 - name: Set up perl - uses: shogo82148/actions-setup-perl@v1.4.0 + uses: shogo82148/actions-setup-perl@v1.15.3 with: perl-version: ${{ matrix.perl }} - run: perl -V @@ -100,7 +100,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up perl - uses: shogo82148/actions-setup-perl@v1.4.0 + uses: shogo82148/actions-setup-perl@v1.15.3 with: perl-version: ${{ matrix.perl }} - run: perl -V From feaa503ab7c89e24a3008433b12b0d8d07e6d681 Mon Sep 17 00:00:00 2001 From: Dave Lambley Date: Mon, 23 May 2022 20:31:31 +0100 Subject: [PATCH 165/165] Try newer Perls --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 6bdba86..2d87a72 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest'] - perl: [ '5.30', '5.28', '5.10', '5.10.0' ] + perl: [ '5.34', '5.32', '5.10', '5.10.0' ] max-parallel: 2 name: Integration test ${{ matrix.perl }} on ${{ matrix.os }} @@ -43,7 +43,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest', 'macos-latest'] - perl: [ '5.30', '5.28', '5.10' ] + perl: [ '5.34', '5.32', '5.10' ] max-parallel: 3 name: Perl unit tests ${{ matrix.perl }} on ${{ matrix.os }}