A summary of data about the Ruby ecosystem.

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

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

Changes between Bunny 3.3.0 and 3.4.0 (Sep 18, 2026)

Ruby 3.2 or Later Is Now Required

required_ruby_version was raised from 3.0 to 3.2.

Client Certificate Chains

If tls_cert points at a certificate bundle (chain), the entire chain will now be sent.

Previously only the first (leaf) certificate was sent, unintentionally.

Contributed by @eglitobias.

GitHub issues: #749, #750

TLS Handshake Now Respects connect_timeout

A peer that accepted the TCP connection but never completed the TLS handshake
could block Bunny::Session#start indefinitely. The handshake now gives up after
connect_timeout and the connection fails over to the next endpoint.

This requires the openssl gem 3.3.0 or later.

- Ruby
Published by eglitobias 20 days ago

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

Changes between Bunny 3.2.0 and 3.3.0 (Aug 29, 2026)

Passive Declarations No Longer Affect Topology Recovery

Bunny::Channel#queue and Bunny::Channel#exchange recorded passively declared
queues and exchanges for topology recovery by mistake. Passive declares are just
existence checks and should not be replayed by topology recovery.

Bunny::Session#queue_exists? and Bunny::Session#exchange_exists? were affected
in the same way, since both are implemented using passive declarations.

Queue Recovery Recorded exclusive and auto_delete in Reverse

Bunny::Channel#queue_declare passed the exclusive and auto_delete properties
to the topology registry in the wrong order, so a queue that used one but not the
other was recovered with both properties flipped.

amq-protocol Bump to 2.9.0

2.9.0 release notes.

This version requires Ruby 3.2.0 or later.

- Ruby
Published by eglitobias about 1 month ago

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

Changes between Bunny 3.1.0 and 3.2.0 (Aug 19, 2026)

Modernization for Ruby 3.x

Contributed by @eglitobias.

GitHub issue: #735

amq-protocol Bump to 2.8.0

This version optimizes deserialization of a few frame types,
most benefitting applications that primarily consume messages.

Bug Fixes

Connection Recovery Survives connection.close During Negotiation and Repeated Failures Mid-Recovery

Overlapping (concurrent) connection recovery attempts could leave a client
permanently disconnected, in particular when target node was put into maintenance mode
which closes all client connections before the node is restarted.

Several changes address this scenario:

  • Connection recovery now uses a mutex to avoid concurrent attempts
  • The connection status is set to :open and the heartbeat sender is started
    only after connection.open-ok is actually received
  • A connection.close received during negotiation with automatic recovery
    enabled is now raised in the recovery thread and handled by the recovery
    retry logic, instead of being delivered to the session error handler
  • Socket read timeout is now applied early, which means that a disabled
    (by maintenance mode) protocol listener would not cause client connections
    to linger waiting for a socket event that will never arrive

In addition, a failure to redeclare a single entity during topology recovery no
longer aborts the entire recovery process and will catch transport timeout
exceptions.

Reader Loop No Longer Crashes on a Frameset for an Already Closed Channel

Session#handle_frameset now guards against a missing channel like Session#handle_frame already did.

GitHub issue: #741

Bunny::Consumer Identity Preserved on Recovery

Consumers registered via Queue#subscribe_with or Channel#basic_consume_with
are no longer replaced with a freshly constructed Bunny::Consumer during
automatic connection recovery.

The original instance is restored, so callbacks such as on_cancellation defined
before recovery continue to function.

Contributed by @jollopre.

GitHub issue: #737

- Ruby
Published by michaelklishin about 1 month ago

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 6 months 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 6 months 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 over 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 over 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 over 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 over 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 almost 4 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 almost 4 years ago