Skip to content

fix(bunny): use consumer channel attribute instead of queue.channel#2370

Open
kivanio wants to merge 2 commits into
open-telemetry:mainfrom
kivanio:fix/bunny-consumer-queue-as-string
Open

fix(bunny): use consumer channel attribute instead of queue.channel#2370
kivanio wants to merge 2 commits into
open-telemetry:mainfrom
kivanio:fix/bunny-consumer-queue-as-string

Conversation

@kivanio

@kivanio kivanio commented May 24, 2026

Copy link
Copy Markdown

What this changes

Patches::Consumer#call currently dereferences queue.channel to get the
Bunny::Channel to pass into with_process_span. That works when the
consumer was registered through Bunny::Queue#subscribe (where queue is a
Bunny::Queue), but fails when the consumer is registered via
Bunny::Channel#basic_consume(queue_name, ...) — where queue is a plain
String.

This is exactly how Hutch and similar
Bunny wrappers subscribe, and Bunny::Consumer#initialize itself documents
both types as valid:

# @param [Bunny::Queue,String] queue Queue messages will be consumed from
def initialize(channel, queue, ...)
  @queue = queue || raise(ArgumentError, "queue is nil")
  ...
end

When a String-queue consumer received a message, the patch raised:

NoMethodError: undefined method 'channel' for an instance of String
  @ opentelemetry-instrumentation-bunny-0.25.0/lib/opentelemetry/
      instrumentation/bunny/patches/consumer.rb:14:in
      'OpenTelemetry::Instrumentation::Bunny::Patches::Consumer#call'

Bunny logged the exception as Uncaught exception from consumer #<Bunny::Consumer ...>
and the delivery was neither processed nor ack'd. The consumer tag stayed
registered with the broker (so the channel looked alive from the broker's
side), but messages piled up indefinitely.

In production we observed 30k+ such errors in 6h on Hutch consumers,
requiring manual rolling restarts to drain the backlog every few hours.

The fix

Bunny::Consumer exposes channel as an attr_reader — the same
Bunny::Channel instance that queue.channel would have returned in the
Queue-object path. Using it directly works for both cases (queue as Queue
object and queue as String) without changing semantics for existing callers.

- with_process_span(queue.channel, tracer, delivery_info, properties) do
+ with_process_span(channel,       tracer, delivery_info, properties) do

Test

Added a regression test in consumer_test.rb that subscribes via
channel.basic_consume(queue.name) (String) to exercise the Hutch-style
scenario end-to-end. The test passes after the fix and fails before it with
the NoMethodError shown above.

The existing test (queue.subscribe-based path) continues to pass.

Checklist

  • Conventional commit message (fix:)
  • Branch from fork, not from main
  • No public API changes
  • Regression test added for the failing scenario

🤖 Generated with Claude Code

`Bunny::Consumer#queue` may be either a `Bunny::Queue` instance or a plain
`String` (queue name) — `Bunny::Consumer#initialize` documents both as valid:

    # @param [Bunny::Queue,String] queue Queue messages will be consumed from

Wrappers like Hutch register consumers via
`Bunny::Channel#basic_consume(queue_name, ...)`, which constructs a
`Bunny::Consumer` whose `queue` attribute is the queue name (a `String`).

When such a consumer received a message, the `with_process_span(queue.channel, ...)`
call in `Patches::Consumer#call` raised:

    NoMethodError: undefined method 'channel' for an instance of String
      @ opentelemetry-instrumentation-bunny-0.25.0/lib/opentelemetry/
          instrumentation/bunny/patches/consumer.rb:14:in
          'OpenTelemetry::Instrumentation::Bunny::Patches::Consumer#call'

Bunny logged the exception as "Uncaught exception from consumer" and the
delivery was neither processed nor ack'd. The consumer tag stayed registered
with the broker (so the channel looked alive), but messages piled up
indefinitely — observed in production with 30k+ errors over 6h on Hutch
consumers, requiring manual `rollout restart` to drain the backlog.

Fix: use the consumer's own `channel` attribute (also `attr_reader` on
`Bunny::Consumer`), which is the same `Bunny::Channel` instance that
`queue.channel` would have returned in the Queue-object path.

Added a regression test that subscribes via `channel.basic_consume(queue.name)`
to exercise the Hutch-style String-queue scenario end-to-end.
@linux-foundation-easycla

linux-foundation-easycla Bot commented May 24, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: kivanio / name: Kivanio Barbosa (f861f73)

Comment on lines +85 to +100
it 'traces consuming when consumer subscribes via queue name (String, e.g. Hutch)' do
queue = channel.queue('', exclusive: true).bind(exchange, routing_key: 'ruby.#')

consumer = channel.basic_consume(queue.name) { |_delivery_info, _properties, _payload| }

exchange.publish('San Diego update', routing_key: 'ruby.news')

# Wait until the publish message reached the consumer
sleep 1.0

channel.basic_cancel(consumer.consumer_tag)

process_span = spans.find { |span| span.name == "#{topic}.ruby.news process" }
_(process_span).wont_be_nil
_(process_span.kind).must_equal(:consumer)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you verify that the queue name is being correctly set as an attribute for this case.

@thompson-tomo thompson-tomo force-pushed the fix/bunny-consumer-queue-as-string branch from 6c051e2 to f861f73 Compare June 13, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants