1

In java RabbitMQ client I can do (code in ruby):

 consumer = QueueingConsumer.new(channel);
 channel.basicConsume(queue_name, true, consumer);
 consumer.nextDelivery.getBody

And then third line blocks thread until message comes. But how to achieve it in Bunny client? I can only use block:

channel.queue('').bind(@x, :routing_key => rk).subscribe(block: true) do |_, _, payload|
  # do something
end

or non blocking pop:

delivery_info, properties, payload = q.pop

Is there a way to achieve it like in jruby client using Bunny? The reason I want is that after receiving a message I would like to continue job in my current context.

Sławosz
  • 11,187
  • 15
  • 73
  • 106
  • I'm not sure what you are asking. Bunny does synchronous requests only. It's always blocking so you can make a request, your code will wait, get its response, then you continue. – the Tin Man May 21 '13 at 13:54
  • You're passing `:block => true` so the calling thread is blocked until the message arrives. Are you expecting something different? – Rob Harrop May 21 '13 at 15:18
  • @RobHarrop I can only block calling thread using block of code, but I would like to access payload directly, how it is done by `consumer.nextDelivery.getBody` in java client – Sławosz May 21 '13 at 15:32

3 Answers3

2

The call to subscribe is blocking due to passing :block => true. If you need to access the payload outside of the block, you can take advantage of Ruby's scoping rules:

the_payload = nil
queue = channel.queue('').bind(@x, :routing_key => rk)
queue.subscribe(block: true) do |delivery_info, _, payload|
  the_payload = payload
  channel.consumers[delivery_info.consumer_tag].cancel
end
# the_payload is now the one received in the block!
Rob Harrop
  • 3,445
  • 26
  • 26
  • Does `channel.consumers[delivery_info.consumer_tag].cancel` cancels the subscribe block and unblock the tread? My current solution is: queue = channel.queue('').bind(@x, :routing_key => rk) msg = nil while !msg do queue = queue.pop[2] end – Sławosz May 21 '13 at 15:54
1

Rob Harrop's answer does cancel the queue, but it didn't end the block for me. The following does both using a ruby Queue

require "thread"

unblock = Queue.new # Ruby Queue, not Bunny::Queue
queue = channel.queue('').bind(@x, :routing_key => rk)
consumer = queue.subscribe do |delivery_info, properties, body|
  # do something
  result = determine_if_it_is_time_to_move_on
  unblock.enq true if result
end

unblock.deq # block until a message is enqueued in the ruby Queue
consumer.cancel
Michael Wasser
  • 1,776
  • 2
  • 21
  • 32
0

I needed to receive a single message from a queue. Bunny's Queue#pop is non-blocking and does not have an option to wait. I also needed to support a timeout, and I ended up implementing this:

require "thread"

mutex = Mutex.new
var = ConditionVariable.new
payload = nil
consumer = queue.subscribe(block: false) do |_, _, x_payload|
  mutex.synchronize do
    payload = x_payload
    var.signal
  end
end

mutex.synchronize do
  deadline = Time.now + 10
  while payload.nil? && (remaining = deadline - Time.now) > 0
    var.wait(mutex, remaining)
  end
end

consumer.cancel
raise "timed out waiting for response" if payload.blank?

Inspired in part by https://spin.atomicobject.com/2017/06/28/queue-pop-with-timeout-fixed/

This code has not been battle-tested. It works on staging with a single message. It may not work at scale. I was going to hide all of this complexity in a monkey-patch to Bunny::Queue. Callers would have seen a simple blocking_pop(timeout:) API.

François Beausoleil
  • 16,265
  • 11
  • 67
  • 90