Class: Bunny::Channel

Overview

Channels in RabbitMQ

AMQP 0.9.1 is a multi-channelled protocol. Channels provide a way to multiplex
a heavyweight TCP/IP connection into several light weight connections.
This makes the protocol more “firewall friendly” since port usage is predictable.
It also means that traffic shaping and other network QoS features can be easily employed.
Channels are independent of each other and can perform different functions simultaneously
with other channels, the available bandwidth being shared between the concurrent activities.

Opening Channels

Channels can be opened either via Bunny::Session#create_channel (sufficient in the majority
of cases) or by instantiating Bunny::Channel directly:

conn=Bunny.newconn.startch=conn.create_channel

This will automatically allocate a channel id.

Closing Channels

Channels are closed via #close. Channels that get a channel-level exception are
closed, too. Closed channels can no longer be used. Attempts to use them will raise
ChannelAlreadyClosed.

ch=conn.create_channelch.close

Higher-level API

Bunny offers two sets of methods on Channel: known as higher-level and lower-level
APIs, respectively. Higher-level API mimics amqp gem API where
exchanges and queues are objects (instance of Exchange and Queue, respectively).
Lower-level API is built around AMQP 0.9.1 methods (commands), where queues and exchanges are
passed as strings (à la RabbitMQ Java client, Langohr and Pika).

Exchange Operations In Higher-level API

#exchange is used to declare exchanges with type specified as a symbol or string.

Channel Qos (Prefetch Level)

It is possible to control how many messages at most a consumer will be given (before it acknowledges
or rejects previously consumed ones). This setting is per channel and controlled via #prefetch.

Channel IDs

Channels are identified by their ids which are integers. Bunny takes care of allocating and
releasing them as channels are opened and closed. It is almost never necessary to specify
channel ids explicitly.

There is a limit on the maximum number of channels per connection, usually 65536. Note
that allocating channels is very cheap on both client and server so having tens, hundreds
or even thousands of channels is not a problem.

Channels and Error Handling

Channel-level exceptions are more common than connection-level ones and often indicate
issues applications can recover from (such as consuming from or trying to delete
a queue that does not exist).

With Bunny, channel-level exceptions are raised as Ruby exceptions, for example,
NotFound, that provide access to the underlying channel.close method
information.

conn=Bunny.newconn.startch=conn.create_channel# we assume the queue exists and has messages
delivery_info,properties,payload=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)ch.basic_ack(delivery_info.delivery_tag)

Ack multiple messages fetched via basic.get

conn=Bunny.newconn.startch=conn.create_channel# we assume the queue exists and has messages
_,_,payload1=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)_,_,payload2=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)delivery_info,properties,payload3=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)# ack all fetched messages up to payload3
ch.basic_ack(delivery_info.delivery_tag,true)

# File 'lib/bunny/channel.rb', line 840defbasic_consume(queue,consumer_tag=generate_consumer_tag,no_ack=false,exclusive=false,arguments=nil,&block)raise_if_no_longer_open!maybe_start_consumer_work_pool!queue_name=ifqueue.respond_to?(:name)queue.nameelsequeueend# helps avoid race condition between basic.consume-ok and basic.deliver if there are messages
# in the queue already. MK.
ifconsumer_tag&&consumer_tag.strip!=AMQ::Protocol::EMPTY_STRINGadd_consumer(queue_name,consumer_tag,no_ack,exclusive,arguments,&block)end@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,queue_name,consumer_tag,false,no_ack,exclusive,false,arguments))beginBunny::Timeout.timeout(wait_on_continuations_timeout,ClientTimeout)do@last_basic_consume_ok=wait_on_continuationsendrescueException=>e# if basic.consume-ok never arrives, unregister the proactively
# registered consumer. MK.
unregister_consumer(@last_basic_consume_ok.consumer_tag)raiseeend# in case there is another exclusive consumer and we get a channel.close
# response here. MK.
raise_if_channel_close!(@last_basic_consume_ok)# covers server-generated consumer tags
add_consumer(queue_name,@last_basic_consume_ok.consumer_tag,no_ack,exclusive,arguments,&block)@last_basic_consume_okend

# File 'lib/bunny/channel.rb', line 896defbasic_consume_with(consumer)raise_if_no_longer_open!maybe_start_consumer_work_pool!# helps avoid race condition between basic.consume-ok and basic.deliver if there are messages
# in the queue already. MK.
ifconsumer.consumer_tag&&consumer.consumer_tag.strip!=AMQ::Protocol::EMPTY_STRINGregister_consumer(consumer.consumer_tag,consumer)end@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,consumer.queue_name,consumer.consumer_tag,false,consumer.no_ack,consumer.exclusive,false,consumer.arguments))beginBunny::Timeout.timeout(wait_on_continuations_timeout,ClientTimeout)do@last_basic_consume_ok=wait_on_continuationsendrescueException=>e# if basic.consume-ok never arrives, unregister the proactively
# registered consumer. MK.
unregister_consumer(@last_basic_consume_ok.consumer_tag)raiseeend# in case there is another exclusive consumer and we get a channel.close
# response here. MK.
raise_if_channel_close!(@last_basic_consume_ok)# covers server-generated consumer tags
register_consumer(@last_basic_consume_ok.consumer_tag,consumer)raise_if_continuation_resulted_in_a_channel_error!@last_basic_consume_okend

#basic_get(queue, opts = {:manual_ack => true}) ⇒ Array

Synchronously fetches a message from the queue, if there are any. This method is
for cases when the convenience of synchronous operations is more important than
throughput.

Examples:

Using Bunny::Channel#basic_get with manual acknowledgements

conn=Bunny.newconn.startch=conn.create_channel# here we assume the queue already exists and has messages
delivery_info,properties,payload=ch.basic_get("bunny.examples.queue1",:manual_ack=>true)ch.acknowledge(delivery_info.delivery_tag)

# File 'lib/bunny/channel.rb', line 600defbasic_get(queue,opts={:manual_ack=>true})raise_if_no_longer_open!unlessopts[:ack].nil?warn"[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."opts[:manual_ack]=opts[:ack]end@connection.send_frame(AMQ::Protocol::Basic::Get.encode(@id,queue,!(opts[:manual_ack])))# this is a workaround for the edge case when basic_get is called in a tight loop
# and network goes down we need to perform recovery. The problem is, basic_get will
# keep blocking the thread that calls it without clear way to constantly unblock it
# from the network activity loop (where recovery happens) with the current continuations
# implementation (and even more correct and convenient ones, such as wait/notify, should
# we implement them). So we return a triple of nils immediately which apps should be
# able to handle anyway as "got no message, no need to act". MK.
@last_basic_get_response=if@connection.open?wait_on_basic_get_continuationselse[nil,nil,nil]endraise_if_continuation_resulted_in_a_channel_error!@last_basic_get_responseend

conn=Bunny.newconn.startch=conn.create_channel# we assume the queue exists and has messages
delivery_info,properties,payload=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)ch.basic_nack(delivery_info.delivery_tag,false,true)

Requeue multiple messages fetched via basic.get

conn=Bunny.newconn.startch=conn.create_channel# we assume the queue exists and has messages
_,_,payload1=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)_,_,payload2=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)delivery_info,properties,payload3=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)# requeue all fetched messages up to payload3
ch.basic_nack(delivery_info.delivery_tag,true,true)

# File 'lib/bunny/channel.rb', line 638defbasic_qos(count,global=false)raiseArgumentError.new("prefetch count must be a positive integer, given: #{prefetch_count}")ifcount<0raiseArgumentError.new("prefetch count must be no greater than #{MAX_PREFETCH_COUNT}, given: #{prefetch_count}")ifcount>MAX_PREFETCH_COUNTraise_if_no_longer_open!@connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id,0,count,global))Bunny::Timeout.timeout(wait_on_continuations_timeout,ClientTimeout)do@last_basic_qos_ok=wait_on_continuationsendraise_if_continuation_resulted_in_a_channel_error!@prefetch_count=count@last_basic_qos_okend

conn=Bunny.newconn.startch=conn.create_channel# we assume the queue exists and has messages
delivery_info,properties,payload=ch.basic_get("bunny.examples.queue3",:manual_ack=>true)ch.basic_reject(delivery_info.delivery_tag,true)

#channel_flow(active) ⇒ AMQ::Protocol::Channel::FlowOk

Note:

Recent (e.g. 2.8.x., 3.x) RabbitMQ will employ TCP/IP-level back pressure on publishers if it detects
that consumers do not keep up with them.

Enables or disables message flow for the channel. When message flow is disabled,
no new messages will be delivered to consumers on this channel. This is typically
used by consumers that cannot keep up with the influx of messages.