AnyEvent-RabbitMQ
view release on metacpan or search on metacpan
lib/AnyEvent/RabbitMQ.pm view on Meta::CPAN
else {
$failure_cb->(@_);
}
},
on_drain => sub {
my ($handle) = @_;
my $self = $weak_self or return;
$self->{drain_condvar}->send
if exists $self->{drain_condvar};
},
peername => $args{host},
$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,);
},
sub {
return $args{timeout};
},
);
return $self;
}
sub server_properties {
return shift->{_server_properties};
}
sub _read_loop {
my ($self, $close_cb, $failure_cb,) = @_;
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];
if (length($data) <= 7) {
$failure_cb->('Broken data was received');
@_ = ($self, $close_cb, $failure_cb,);
goto &_read_loop;
}
my ($type_id, $channel, $length,) = unpack 'CnN', substr $data, 0, 7, '';
if (!defined $type_id || !defined $channel || !defined $length) {
$failure_cb->('Broken data was received');
@_ = ($self, $close_cb, $failure_cb,);
goto &_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_timer};
if ($self->{verbose}) {
warn '[C] <-- [S] ', Dumper($frame),
'-----------', "\n";
}
my $id = $frame->channel;
if (0 == $id) {
if ($frame->type_id == 8) {
# Heartbeat, no action needs taking.
}
else {
return unless $self->_check_close_and_clean($frame, $close_cb,);
$self->{_queue}->push($frame);
}
} else {
my $channel = $self->{_channels}->{$id};
if (defined $channel) {
$channel->push_queue_or_consume($frame, $failure_cb);
} else {
$failure_cb->('Unknown channel id: ' . $frame->channel);
}
}
@_ = ($self, $close_cb, $failure_cb,);
goto &_read_loop;
});
});
return $self;
}
sub _check_close_and_clean {
my $self = shift;
my ($frame, $close_cb,) = @_;
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');
}
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;
}
return 1;
}
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->{_handle}->push_shutdown;
$self->{_state} = _ST_CLOSED;
$close_cb->($why);
return;
}
sub _start {
my $self = shift;
my %args = @_;
if ($self->{verbose}) {
warn 'post header', "\n";
}
$self->{_handle}->push_write(Net::AMQP::Protocol->header);
$self->_push_read_and_valid(
'Connection::Start',
sub {
my $frame = shift;
my @mechanisms = split /\s/, $frame->method_frame->mechanisms;
return $args{on_failure}->('AMQPLAIN is not found in mechanisms')
if none {$_ eq 'AMQPLAIN'} @mechanisms;
my @locales = split /\s/, $frame->method_frame->locales;
return $args{on_failure}->('en_US is not found in locales')
if none {$_ eq 'en_US'} @locales;
$self->{_server_properties} = $frame->method_frame->server_properties;
$self->_push_write(
Net::AMQP::Protocol::Connection::StartOk->new(
client_properties => {
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,
},
lib/AnyEvent/RabbitMQ.pm view on Meta::CPAN
$args{on_failure},
);
return $self;
}
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;
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
}
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;
}
$self->{_channel_max} = $tune{channel_max} || $DEFAULT_CHANNEL_MAX;
$self->_push_write(
Net::AMQP::Protocol::Connection::TuneOk->new(%tune,)
);
if ($tune{heartbeat} > 0) {
$self->_start_heartbeat($tune{heartbeat}, %args,);
}
$self->_open(%args,);
},
$args{on_failure},
);
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->_server_closed($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 = @_;
$self->_push_write_and_read(
'Connection::Open',
{
virtual_host => $args{vhost},
insist => 1,
},
'Connection::OpenOk',
sub {
$self->{_state} = _ST_OPEN;
$self->{_login_user} = $args{user};
$args{on_success}->($self);
},
$args{on_failure},
);
return $self;
}
sub close {
return if in_global_destruction;
my $self = shift;
my %args = $self->_set_cbs(@_);
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();
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() },
);
}
}
lib/AnyEvent/RabbitMQ.pm view on Meta::CPAN
return $failure_cb->('Received data is not method frame')
if !$frame->isa('Net::AMQP::Frame::Method');
my $method_frame = $frame->method_frame;
for my $exp_elem (@$exp) {
return $cb->($frame)
if $method_frame->isa('Net::AMQP::Protocol::' . $exp_elem);
}
$failure_cb->(
$method_frame->isa('Net::AMQP::Protocol::Channel::Close')
? 'Channel closed'
: 'Expected ' . join(',', @$exp) . ' but got ' . ref($method_frame)
);
});
}
sub _push_write {
my $self = shift;
my ($output, $id,) = @_;
if ($output->isa('Net::AMQP::Protocol::Base')) {
$output = $output->frame_wrap;
}
$output->channel($id || 0);
if ($self->{verbose}) {
warn '[C] --> [S] ', Dumper($output);
}
$self->{_handle}->push_write($output->to_raw_frame())
if $self->{_handle}; # Careful - could have gone (global destruction)
return;
}
sub _set_cbs {
my $self = shift;
my %args = @_;
$args{on_success} ||= sub {};
$args{on_failure} ||= sub { die @_ unless in_global_destruction };
return %args;
}
sub _check_open {
my $self = shift;
my ($failure_cb) = @_;
return 1 if $self->is_open;
$failure_cb->('Connection has already been closed');
return 0;
}
sub drain_writes {
my ($self, $timeout) = shift;
$self->{drain_condvar} = AnyEvent->condvar;
if ($timeout) {
$self->{drain_timer} = AnyEvent->timer( after => $timeout, sub {
$self->{drain_condvar}->croak("Timed out after $timeout");
});
}
$self->{drain_condvar}->recv;
delete $self->{drain_timer};
}
sub DESTROY {
my $self = shift;
$self->close() unless in_global_destruction;
return;
}
1;
__END__
=head1 NAME
AnyEvent::RabbitMQ - An asynchronous and multi channel Perl AMQP client.
=head1 SYNOPSIS
use AnyEvent::RabbitMQ;
my $cv = AnyEvent->condvar;
my $ar = AnyEvent::RabbitMQ->new->load_xml_spec()->connect(
host => 'localhost',
port => 5672,
user => 'guest',
pass => 'guest',
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 },
nodelay => 1, # Reduces latency by disabling Nagle's algorithm
on_success => sub {
my $ar = shift;
$ar->open_channel(
on_success => sub {
my $channel = shift;
$channel->declare_exchange(
exchange => 'test_exchange',
on_success => sub {
$cv->send('Declared exchange');
},
on_failure => $cv,
);
},
on_failure => $cv,
on_close => sub {
my $method_frame = shift->method_frame;
die $method_frame->reply_code, $method_frame->reply_text;
},
);
},
on_failure => $cv,
on_read_failure => sub { die @_ },
on_return => sub {
my $frame = shift;
die "Unable to deliver ", Dumper($frame);
},
on_close => sub {
my $why = shift;
( run in 0.827 second using v1.01-cache-2.11-cpan-98e64b0badf )