Is bunny thread safe?

1,439 views
Skip to first unread message

Owen Ou

unread,
Apr 2, 2013, 1:34:37 PM4/2/13
to ruby...@googlegroups.com
Hi all,

I am trying to use bunny with the latest Rails. I am wondering whether bunny is thread safe to publish and consume messages. I encapsulate bunny like this for publication:

class Messenger
  def self.publish(key, value)
    instance.publish(key, value)
  end

  def self.instance
    @instance ||= begin
                    conn = Bunny.new
                    conn.start

                    ch = conn.create_channel
                    ex = ch.topic('topics', auto_delete: true)
                    new ex
                  end
  end

  attr_reader :exchange

  def initialize(exchange)
    @exchange = exchange
  end

  def publish(key, value)
    exchange.publish(value, routing_key: key)
  end
end 

Is exchange#publish thread safe?

Cheers,
Owen

Michael Klishin

unread,
Apr 2, 2013, 2:16:31 PM4/2/13
to ruby...@googlegroups.com

2013/4/2 Owen Ou <jing...@gmail.com>
Is exchange#publish thread safe?

1. What version of Bunny
2. Define "thread safe"

For publishing, there are at least 2 potential concurrency hazards:

 * Sharing a channel across threads may result in incorrectly interleaved frames on the wire. Bunny 0.9 handles that and has plenty of stress tests.
 * Recovering from connection losses with multiple threads may lead to unexpected connection state changes. This should be fine in most cases but we haven't tried to make it absolutely correct because this would make little practical difference at a noticeable development time cost.

Consumer safety is a completely different issue as consumers don't really share any state but if you use a thread pool
with more than 1 thread, dispatched messages will be processed concurrently and Bunny cannot even theoretically
solve this for you. If you need strict ordering of processing, it's up to you.

So, for publishing from a Rails app, the answer for Bunny 0.9 is "yes", although the rule of thumb has always been "don't share channels
between threads if you can". But Rails does not really give you much choice.

Consuming within a Rails app only makes sense for temporary (RPC) responses which don't need ordering, so I can't
think of any realistic hazard there.
--
MK

http://github.com/michaelklishin
http://twitter.com/michaelklishin

Owen Ou

unread,
Apr 2, 2013, 2:43:14 PM4/2/13
to ruby...@googlegroups.com
Thanks for the clarification!

1. Bunny 0.9
2. Multiple (request) threads publish messages. Maybe not applicable to consumers.

So for publisher, as you mentioned "don't share channels between threads if you can", what would be the "right" approach? Do you have an example on how to do that in Rails? Should I put mutex around the channel/exchange object?

For consumer, I am using pure Ruby to consume the messages. Is the execution of the "subscribe" block in a thread pool as in

q.subscribe(:block => true, :ack => true) do |delivery_info, properties, payload|
  puts "Received #{payload}, message properties are #{properties.inspect}"
end

Thanks,
Owen

Michael Klishin

unread,
Apr 2, 2013, 2:50:44 PM4/2/13
to ruby...@googlegroups.com

2013/4/2 Owen Ou <jing...@gmail.com>

So for publisher, as you mentioned "don't share channels between threads if you can", what would be the "right" approach? Do you have an example on how to do that in Rails?

Rails doesn't give you much choice here, as far as I know. If there is a way to perform per-thread initialization, you probably
can store a channel in a thread local variable.

You can also open a channel for each request and use a predeclared
exchange or an option to avoid declaration. But that will affect both your latency and (if you are not careful)
availability of the entire request handler.
 
Should I put mutex around the channel/exchange object?

Connection already does that. Every channel has a mutex associated with it.

Owen Ou

unread,
Apr 2, 2013, 4:18:36 PM4/2/13
to ruby...@googlegroups.com
Thanks Michael!

Ruby has thread local though. You mean something like this (http://en.wikipedia.org/wiki/Thread-local_storage#Ruby)? 

Thread.current[:channel] = bunny_channel

You keep mentioning channel here. Should I store channel or exchange as a thread local?

Cheers,
Owen

Michael Klishin

unread,
Apr 2, 2013, 5:08:30 PM4/2/13
to ruby...@googlegroups.com

2013/4/3 Owen Ou <jing...@gmail.com>

Ruby has thread local though. You mean something like this (http://en.wikipedia.org/wiki/Thread-local_storage#Ruby)? 

Thread.current[:channel] = bunny_channel


Yes, Ruby has thread locals but does Rails give you a way to pre-initialize something in every thread it
will use? I doubt it.
 
You keep mentioning channel here. Should I store channel or exchange as a thread local?

You need to use 1 channel per thread.

Owen Ou

unread,
Apr 2, 2013, 5:43:55 PM4/2/13
to ruby...@googlegroups.com
Sorry. I feel a bit confused. Are we talking about not sharing one channel across all requests? I thought you meant I could store channel in a thread local and share it across requests.

Would storing channel per thread (request) be slowing the request down? Looks like creating a channel will block until response comes back from rabbitmq (https://github.com/ruby-amqp/bunny/blob/master/lib/bunny/session.rb#L172-L176). By following your logic, I should start the session in a rails initializer and use that create a channel every request?

Do you think it makes sense to have a pool of channels and share them for all requests?

Owen Ou

unread,
Apr 2, 2013, 5:47:50 PM4/2/13
to ruby...@googlegroups.com
It might be possible to store the channel as a rack env variable and that's used per request as I understand

Michael Klishin

unread,
Apr 2, 2013, 6:07:00 PM4/2/13
to ruby...@googlegroups.com

2013/4/3 Owen Ou <jing...@gmail.com>

It might be possible to store the channel as a rack env variable and that's used per request as I understand

if opening a new channel for every request is acceptable for you, using middleware is a good idea

Owen Ou

unread,
Apr 2, 2013, 6:17:25 PM4/2/13
to ruby...@googlegroups.com
Do you think it makes sense to have a pool of channels shared across requests? Or maybe create a channel for the request and close it immediately after the request is done

--
--
Documentation guides: http://bit.ly/rubyamqp
Code examples: http://bit.ly/amq-gem-examples
API reference: http://bit.ly/mDm1JE
 
Drop by #rabbitmq on irc.freenode.net
Bug tracker: https://github.com/ruby-amqp/amqp/issues
 
Post to the group: ruby...@googlegroups.com | unsubscribe: ruby-amqp+...@googlegroups.com
 
---
You received this message because you are subscribed to a topic in the Google Groups "Ruby AMQP ecosystem" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ruby-amqp/aqs1t5BOdQI/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to ruby-amqp+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Michael Klishin

unread,
Apr 2, 2013, 6:39:09 PM4/2/13
to ruby...@googlegroups.com

2013/4/3 Owen Ou <jing...@gmail.com>

Do you think it makes sense to have a pool of channels shared across requests? Or maybe create a channel for the request and close it immediately after the request is done

Pooling is hard to get right. I don't know your requirements but opening a channel is one network roundtrip over an already
opened TCP connection with tiny payload. If your RabbitMQ node is close, it may be fine even pretty demanding
environments.

In the latter case you will have to profile the whole system anyway.

Owen Ou

unread,
Apr 2, 2013, 6:49:11 PM4/2/13
to ruby...@googlegroups.com
Would you minding sharing how you would use bunny 0.9 with Rails? I google around and found this example: https://github.com/lshift/rabbitmq-service-rails-sample/blob/master/app/controllers/home_controller.rb#L19-L29. It doesn't look right to me though - creating a bunny instance on every request

Thanks a lot,
Owen

Michael Klishin

unread,
Apr 2, 2013, 6:50:04 PM4/2/13
to ruby...@googlegroups.com

2013/4/3 Owen Ou <jing...@gmail.com>

It doesn't look right to me though - creating a bunny instance on every request

Create it in an initializer, store it in a class variable or even global.

Owen Ou

unread,
Apr 2, 2013, 7:06:41 PM4/2/13
to ruby...@googlegroups.com
How about channels? Would you recommend to create one per request and close it after it's done? If I don't close the channel, won't it have memory leak since it caches in a local variable @channels (https://github.com/ruby-amqp/bunny/blob/master/lib/bunny/session.rb#L178)?

That's why I am thinking of either closing a channel after use or build a connection pool with something like (https://github.com/mperham/connection_pool).

Sorry for asking the same question again and again. It's just I still couldn't find the right way to use bunny.

Cheers,
Owen

Michael Klishin

unread,
Apr 2, 2013, 7:11:23 PM4/2/13
to ruby...@googlegroups.com

2013/4/3 Owen Ou <jing...@gmail.com>

How about channels? Would you recommend to create one per request and close it after it's done? If I don't close the channel, won't it have memory leak since it caches in a local variable @channels (https://github.com/ruby-amqp/bunny/blob/master/lib/bunny/session.rb#L178)?

Yes, you need to close channels if you create them per request.

Steven Yue

unread,
Apr 24, 2013, 11:31:04 AM4/24/13
to ruby...@googlegroups.com
Hi, 

I had the similar question as Owen. After reading through these posts, my understand is :

I can put the code like:  
   $b = Bunny.new(... ...)
   $b.start

into an initializer file, to make it as a global variable.

Then, for each request I need to create a channel, declare an exchange and do the publish and other stuff, and no worry about the thread stuff?


Or  should I even put the $b.start inside of each request and stop it every time?

Michael Klishin

unread,
Apr 24, 2013, 11:36:43 AM4/24/13
to ruby...@googlegroups.com

2013/4/24 Steven Yue <jinc...@gmail.com>

Then, for each request I need to create a channel, declare an exchange and do the publish and other stuff, and no worry about the thread stuff?


Yes, although that's not optimal (you will have a quick round-trip for every request), until Rails allows you
to initialize things "per thread", there is no straightforward way (thread pools in the Ruby worlds are not common
and there is no good correct generic one).

In practice, I think even sharing a channel will be fine for publishing from a Web app, as Bunny tries to synchronize most obvious cases. But it's a better idea to not share channels. Sharing connection is perfectly fine.
 

Or  should I even put the $b.start inside of each request and stop it every time?

No, absolutely no need to do that.

Steven Yue

unread,
Apr 24, 2013, 11:39:05 AM4/24/13
to ruby...@googlegroups.com
Thanks for your quick reply, I got it :)
Reply all
Reply to author
Forward
0 new messages