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.
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.newsignature has changed: the third positional argument is nowopts = {}(an option hash) instead of a consumer work pool.
UseBunny::Session#create_channelor pass the work pool asopts[:work_pool]VersionedDeliveryTagremoved: delivery tags are now raw integersConsumer#recover_from_network_failurewas removed: topology recovery is now handled byBunny::SessionviaTopologyRegistryExchange#recover_from_network_failurewas removed: see aboveQueue#recover_from_network_failureandQueue#recover_bindingswere 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::QUORUMBunny::Queue::Types::CLASSICBunny::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