A summary of data about the Ruby ecosystem.

Recent Releases of https://github.com/ruby-amqp/bunny

https://github.com/ruby-amqp/bunny - 3.1.0

Changes between Bunny 3.0.0 and 3.1.0 (Apr 1, 2026)

on_error Callback Runs in a Separate Thread

Channel#on_error callbacks are now invoked in a separate thread
when triggered by the reader loop. This allows callbacks to perform
blocking operations such as Channel#reopen without deadlocking.

Internal Connection Recovery Improvements

A detected connection failure now immediately marks all channels
on the connection as closed.

This has no practical consequences for most applications but
helps make connection recovery tests more robust.

- Ruby
Published by michaelklishin 29 days ago

https://github.com/ruby-amqp/bunny - 3.0.0

Changes between Bunny 2.24.0 and 3.0.0 (March 31, 2026)

Topology Recovery Improvements

Back in 2013-2014, RabbitMQ Java client's connection recovery was heavily
influenced by what was available in Bunny.

In 2025, Bunny starts adopting the features Java client has developed
over the years, starting with connection-level topology tracking.

Now all exchanges, queues, bindings recorded for topology recovery are stored
and maintained by Bunny::Session and not Bunny::Channel. This makes
recovery somewhat simpler and eliminates a class of problems where,
say, a queue was declared on one channel, deleted on another, and re-created
by Bunny's connection recovery contrary to the user's intent.

The only potentially breaking change here is: the client now intentionally skips
tracking queue and exchange declarations with passive: true.

GitHub issues: #704, #711

Reduced Connection Recovery Logging

On connections that have connection recovery enabled, certain I/O exceptions
are now logged at debug level to reduce log noise.

GitHub issue: #711

Topology Recovery Filters

class ExampleTopologyFilter < Bunny::TopologyRecoveryFilter
  def filter_queues(qs)
    qs.filter { |rq| rq.name.start_with?(/^filter-me/) }
  end

  def filter_exchanges(xs)
    xs.filter { |rx| rx.name.start_with?(/^filter-me/) }
  end

  def filter_queue_bindings(bs)
    bs.filter { |rb| rb.destination.start_with?(/^filter-me/) }
  end

  def filter_exchange_bindings(bs)
    bs.filter { |rb| rb.destination.start_with?(/^filter-me/) }
  end

  def filter_consumers(bs)
    bs.filter { |rc| rc.consumer_tag.start_with?(/filter-me/) }
  end
end

tf = ExampleTopologyFilter.new
# Will use the above filter to determine what queues, exchanges, bindings,
# and consumers must be retained (recovered) during topology recovery
c = Bunny::Session.new(topology_recovery_filter: tf)
c.start

Removed Versioned Delivery Tags

Versioned delivery tags introduced about as many problems as they have solved.

Originally introduced in 2013 shortly after automatic connection recovery,
they have been a polarizing feature for years.

3.0 is a good opportunity to remove them.

GitHub issue: #700.

Significant Publisher Performance Improvements

Publisher performance improvements (100K messages, with amq-protocol 2.4.0 or later)
with automatic publisher confirm tracking enabled (documented below):

Approach Throughput vs 2.x confirms
2.x wait_for_confirms ~11k msg/s baseline
3.x single publish ~35k msg/s 320%
3.x basic_publish_batch(500) ~43k msg/s 390%
3.x basic_publish_batch(1000) ~45k msg/s 410%
3.x basic_publish_batch(2000) ~44k msg/s 400%
3.x basic_publish_batch(3000) ~43k msg/s 390%

Bunny 3.0's confirm tracking is 3-4x faster than 2.x. Batch size of 1000
provides optimal throughput. Avoid batches over 3000 (they will perform worse due to
connection flow control on the RabbitMQ end).

To migrate from 2.x, simply replace Channel#confirm_select calls with Channel#confirm_select(tracking: true).
That's it.

In addition, Bunny::Channel#basic_publish_batch benefits further from the write hot path optimizations
that do not benefit Bunny::Channel#basic_publish much.

Publisher Confirm Tracking

Bunny now supports publisher confirm
tracking, inspired by the .NET client 7.x
and Swift Bunny.

Use basic_publish_batch for optimal throughput (batch sizes of 500-3000 recommended):

ch.confirm_select(tracking: true)

messages.each_slice(1000) do |batch|
  ch.basic_publish_batch(batch, "", queue.name)
end

Single-message publishing is also supported but slower:

ch.confirm_select(tracking: true)
messages.each { |msg| x.publish(msg, routing_key: q.name) }

When tracking is set to true, outstanding_limit defaults to 1000 (this is an optimal value according to the benchmarks, see below).
This provides backpressure when too many messages are unconfirmed.

If the broker nacks a message, a Bunny::MessageNacked exception is raised.

Performance (100K messages, with amq-protocol 2.7.0):

Approach Throughput vs 2.x confirms
2.x wait_for_confirms ~11k msg/s baseline
3.x single publish ~35k msg/s 320%
3.x basic_publish_batch(500) ~43k msg/s 390%
3.x basic_publish_batch(1000) ~45k msg/s 410%
3.x basic_publish_batch(2000) ~44k msg/s 400%
3.x basic_publish_batch(3000) ~43k msg/s 390%

Bunny 3.0's confirm tracking is 3-4x faster than 2.x. Batch size of 1000
provides optimal throughput. Avoid batches over 3000 (they will perform worse due to
connection flow control on the RabbitMQ end).

To migrate from 2.x, simply replace Channel#confirm_select calls with Channel#confirm_select(tracking: true).
With that single line you get automatic backpressure via publisher confirms and three times better throughput.

Important design note: unlike the .NET client 7.x and Swift Bunny, which both pause the caller per-message using the async/await
features in those languages (this is very cheap: just suspends a task), Bunny in Ruby uses a watermark approach
with a shared condition variable. This avoids per-message mutex contention that has a dramatic negative performance effect.

Consumer Delivery Performance Optimizations

Several optimizations to reduce overhead in the consumer delivery hot path:

  • DeliveryInfo: hash representation is now lazily created only when accessed via
    to_hash, each, or []; direct method access (e.g., delivery_tag, routing_key)
    no longer allocates a hash, providing a roughly x2 speedup on microbenchmarks of very simplistic consumers

  • Consumer lookup caching: channels now cache the last consumer lookup, benefiting
    the common single-consumer-per-channel pattern

  • Frame header buffer reuse: the transport layer now reuses a buffer when reading
    frame headers, reducing per-frame allocations

Exchange Type Constants

Bunny::Exchange now provides constants for all built-in and commonly used
exchange types: TYPE_DIRECT, TYPE_FANOUT, TYPE_TOPIC, TYPE_HEADERS,
TYPE_MODULUS_HASH, TYPE_LOCAL_RANDOM, TYPE_CONSISTENT_HASH, TYPE_RANDOM.

Tanzu RabbitMQ Delayed Queue Support

Bunny::Queue::Types::DELAYED and Channel#delayed_queue declare a
Tanzu RabbitMQ delayed queue with optional :delayed_retry_type,
:delayed_retry_min, and :delayed_retry_max options.

Tanzu RabbitMQ JMS Queue Support

Bunny::Queue::Types::JMS and Channel#jms_queue declare a
Tanzu RabbitMQ JMS queue with optional :selector_fields and
:selector_field_max_bytes options.

Channel#reopen

A new method that reopens a channel after a server-initiated closure
(e.g. due to a consumer delivery acknowledgement timeout or an unknown delivery tag).
The channel is reopened on the same connection, reusing its original channel id,
and its prefetch, confirm, and transactional settings are recovered.

Session#recover_channel_topology

Recovers topology (exchanges, queues, bindings, consumers) for a single channel.
Intended for use after Channel#reopen.

amq-protocol Bumped to 2.7.0 (or Later)

Bunny now requires amq-protocol 2.7.0 or later for the Channel::Close
predicate methods (#unknown_delivery_tag?, #delivery_ack_timeout?, #message_too_large?)
that Bunny used to reinvent (with regular expression matches on reply_text)

Limit Hostname Resolution Time

Bunny now configures its TCP socket to limit the hostname resolution time,
assuming that the OS kernel supports the underlying socket option.

Breaking Changes

Except for the VersionedDeliveryTag removal, all breaking changes in this release are minor and
do not affect most codebases that use Bunny.

  • Bunny::Channel.new signature has changed: the third positional argument is now opts = {} (an option hash) instead of a consumer work pool.
    Use Bunny::Session#create_channel or pass the work pool as opts[:work_pool]
  • VersionedDeliveryTag removed: delivery tags are now raw integers
  • Consumer#recover_from_network_failure was removed: topology recovery is now handled by Bunny::Session via TopologyRegistry
  • Exchange#recover_from_network_failure was removed: see above
  • Queue#recover_from_network_failure and Queue#recover_bindings were removed: see above

- Ruby
Published by michaelklishin about 1 month ago

https://github.com/ruby-amqp/bunny - 2.24.0

Changes between Bunny 2.23.0 and 2.24.0 (March 23, 2025)

An Option that Will Cancel Consumers Before a Channel Closing is Initiated

Sometimes it makes more sense to avoid any possible in-flight deliveires
rather than trying to deal with them in a reasonable way, even though
all outstanding deliveries that were not confirmed will be requeued after
a channel closure event.

Here is how this setting is supposed to be used:

c = Bunny.new; c.start
ch = c.create_channel.configure do |new_ch|
  new_ch.prefetch(10)
  new_ch.cancel_consumers_before_closing!
end

q = ch.quorum_queue("a.queue")

This setting is opt-in and disabled by default.

- Ruby
Published by michaelklishin about 1 year ago

https://github.com/ruby-amqp/bunny - 2.23.0

Changes between Bunny 2.22.0 and 2.23.0 (July 1, 2024)

Bunny::Channel#on_error invoked for delivery acknowledgement timeouts

Contributed by @dchompd.

GitHub issue: #684

Heartbeat sender now uses a monotonic clock function

Contributed by @blowfishpro.

GitHub issue: #676

- Ruby
Published by michaelklishin almost 2 years ago

https://github.com/ruby-amqp/bunny - 2.22.0

Changes between Bunny 2.21.0 and 2.22.0 (June 12, 2023)

New Connection Callback: :recovery_attempts_exhausted

A new connection callback, :recovery_attempts_exhausted, is invoked when
all allowed recovery attempts have failed.

Contributed by @Schmitze333.

GitHub issue: #666

Bunny::Channel#default_exchange Caching

Bunny::Channel#default_exchange now caches the Bunny::Exchange instance
it returns.

GitHub issue: #661

- Ruby
Published by michaelklishin almost 2 years ago

https://github.com/ruby-amqp/bunny - v2.21.0

Changes between Bunny 2.21.0 and 2.22.0 (June 12, 2023)

New Connection Callback: :recovery_attempts_exhausted

A new connection callback, :recovery_attempts_exhausted, is invoked when
all allowed recovery attempts have failed.

Contributed by @Schmitze333.

GitHub issue: #666

Bunny::Channel#default_exchange Caching

Bunny::Channel#default_exchange now caches the Bunny::Exchange instance
it returns.

GitHub issue: #661

- Ruby
Published by michaelklishin almost 3 years ago

https://github.com/ruby-amqp/bunny - 2.20.0

Changes between Bunny 2.19.x and 2.20.0 (December 15, 2022)

New Bunny::Channel helpers for declaring quorum queues and streams

Introduce a few helpers for quorum queues, streams, and durable client-named
queues in general, similar in spirit to Bunny::Channel#temporary_queue
for temporary queues.

Bunny::Channel#quorum_queue

Bunny::Channel#quorum_queue accepts a name (server-generated names are not supported)
and a set of options arguments,
and declares a quorum queue.

Durability, exclusivity, and auto-delete properties will be ignored: it only makes
sense for quorum queues to be durable, non-exclusive and non-auto-delete since
they are all about data safety.

Bunny::Channel#stream

Bunny::Channel#stream accepts a name (server-generated names are not supported)
and a set of options arguments,
and declares a stream that Bunny
can use over AMQP 0-9-1 as if it was a replicated queue (without any stream-specific operations).

Durability, exclusivity, and auto-delete properties will be ignored: it only makes
sense for streams to be durable, non-exclusive and non-auto-delete since they are by definition a durable replicated data structure for non-transient
(or at least not entirely transient) data.

Bunny::Channel#durable_queue

Bunny::Channel#durable_queue accepts a name (server-generated names are not supported),
a queue type (one of: Bunny::Queue::Types::QUORUM, Bunny::Queue::Types::CLASSIC, Bunny::Queue::Types::STREAM), and a set of options arguments,
and declares a quorum queue.

Durability, exclusivity, and auto-delete properties will be ignored by design, just
like Bunny::Channel#temporary_queue overrides them to declare transient queues.

Bunny::Queue::Types

Bunny::Queue::Types is a module with a few constants that represent currently available
queue types:

  • Bunny::Queue::Types::QUORUM
  • Bunny::Queue::Types::CLASSIC
  • Bunny::Queue::Types::STREAM

Their names are self-explanatory.

Test Files Left Out of .gem File

Test files (specs) are no longer included into the .gem file.

Contributed by Alexey @alexeyschepin Schepin.

GitHub issue: #621

- Ruby
Published by michaelklishin over 3 years ago

https://github.com/ruby-amqp/bunny - 2.20.1

Changes between Bunny 2.20.x and 2.20.1 (December 19, 2022)

Gracefully Handles a Race Condition Between Server-sent and Client Channel Closure

Contributed by @milgner.

GitHub issue: #644

- Ruby
Published by michaelklishin over 3 years ago