bunny 0.9.0.pre8 → 0.9.0.pre9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog.md +31 -0
- data/README.md +1 -1
- data/examples/guides/extensions/dead_letter_exchange.rb +1 -1
- data/lib/bunny/channel.rb +9 -9
- data/lib/bunny/concurrent/continuation_queue.rb +28 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +56 -0
- data/lib/bunny/exceptions.rb +4 -0
- data/lib/bunny/heartbeat_sender.rb +3 -1
- data/lib/bunny/session.rb +61 -10
- data/lib/bunny/socket.rb +1 -1
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/basic_nack_spec.rb +1 -1
- data/spec/higher_level_api/integration/basic_publish_spec.rb +29 -0
- data/spec/higher_level_api/integration/connection_spec.rb +1 -1
- data/spec/higher_level_api/integration/dead_lettering_spec.rb +1 -1
- data/spec/higher_level_api/integration/heartbeat_spec.rb +31 -0
- data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +49 -0
- metadata +8 -2
    
        data/ChangeLog.md
    CHANGED
    
    | @@ -1,5 +1,36 @@ | |
| 1 | 
            +
            ## Changes between Bunny 0.9.0.pre8 and 0.9.0.pre9
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ### More Reliable Heartbeat Sender
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Heartbeat sender no longer slips into an infinite loop if it encounters an exception.
         | 
| 6 | 
            +
            Instead, it will just stop (and presumably re-started when the network error recovery
         | 
| 7 | 
            +
            kicks in or the app reconnects manually).
         | 
| 8 | 
            +
             | 
| 9 | 
            +
             | 
| 10 | 
            +
            ### Network Recovery After Delay
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Network reconnection now kicks in after a delay to avoid aggressive
         | 
| 13 | 
            +
            reconnections in situations when we don't want to endlessly reconnect
         | 
| 14 | 
            +
            (e.g. when the connection was closed via the Management UI).
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            The `:network_recovery_interval` option passed to `Bunny::Session#initialize` and `Bunny.new`
         | 
| 17 | 
            +
            controls the interval. Default is 5 seconds.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
             | 
| 20 | 
            +
            ### Default Heartbeat Value Is Now Server-Defined
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            Bunny will now use heartbeat value provided by RabbitMQ by default.
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| 25 | 
            +
             | 
| 1 26 | 
             
            ## Changes between Bunny 0.9.0.pre7 and 0.9.0.pre8
         | 
| 2 27 |  | 
| 28 | 
            +
            ### Stability Improvements
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            Several stability improvements in the network
         | 
| 31 | 
            +
            layer, connection error handling, and concurrency hazards.
         | 
| 32 | 
            +
             | 
| 33 | 
            +
             | 
| 3 34 | 
             
            ### Automatic Connection Recovery Can Be Disabled
         | 
| 4 35 |  | 
| 5 36 | 
             
            Automatic connection recovery now can be disabled by passing
         | 
    
        data/README.md
    CHANGED
    
    | @@ -36,7 +36,7 @@ gem install bunny --pre | |
| 36 36 | 
             
            To use Bunny 0.9.x in a project managed with Bundler:
         | 
| 37 37 |  | 
| 38 38 | 
             
            ``` ruby
         | 
| 39 | 
            -
            gem "bunny", ">= 0.9.0. | 
| 39 | 
            +
            gem "bunny", ">= 0.9.0.pre8" # optionally: , :git => "git://github.com/ruby-amqp/bunny.git", :branch => "master"
         | 
| 40 40 | 
             
            ```
         | 
| 41 41 |  | 
| 42 42 |  | 
| @@ -23,7 +23,7 @@ sleep 0.2 | |
| 23 23 | 
             
            delivery_info, _, _ = q.pop(:ack => true)
         | 
| 24 24 | 
             
            puts "#{dlq.message_count} messages dead lettered so far"
         | 
| 25 25 | 
             
            puts "Rejecting a message"
         | 
| 26 | 
            -
            ch.nack(delivery_info.delivery_tag | 
| 26 | 
            +
            ch.nack(delivery_info.delivery_tag)
         | 
| 27 27 | 
             
            sleep 0.2
         | 
| 28 28 | 
             
            puts "#{dlq.message_count} messages dead lettered so far"
         | 
| 29 29 |  | 
    
        data/lib/bunny/channel.rb
    CHANGED
    
    | @@ -447,13 +447,13 @@ module Bunny | |
| 447 447 | 
             
                # supports rejecting multiple messages at once, and is usually preferred.
         | 
| 448 448 | 
             
                #
         | 
| 449 449 | 
             
                # @param [Integer] delivery_tag Delivery tag to reject
         | 
| 450 | 
            -
                # @param [Boolean] requeue      Should this message be requeued instead of dropping it?
         | 
| 451 450 | 
             
                # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be rejected as well?
         | 
| 451 | 
            +
                # @param [Boolean] requeue  (false) Should this message be requeued instead of dropping it?
         | 
| 452 452 | 
             
                # @see Bunny::Channel#ack
         | 
| 453 453 | 
             
                # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
         | 
| 454 454 | 
             
                # @api public
         | 
| 455 | 
            -
                def nack(delivery_tag,  | 
| 456 | 
            -
                  basic_nack(delivery_tag,  | 
| 455 | 
            +
                def nack(delivery_tag, multiple = false, requeue = false)
         | 
| 456 | 
            +
                  basic_nack(delivery_tag, multiple, requeue)
         | 
| 457 457 | 
             
                end
         | 
| 458 458 |  | 
| 459 459 | 
             
                # @endgroup
         | 
| @@ -713,7 +713,7 @@ module Bunny | |
| 713 713 | 
             
                #   ch    = conn.create_channel
         | 
| 714 714 | 
             
                #   q.subscribe do |delivery_info, properties, payload|
         | 
| 715 715 | 
             
                #     # requeue the message
         | 
| 716 | 
            -
                #     ch.basic_nack(delivery_info.delivery_tag, true)
         | 
| 716 | 
            +
                #     ch.basic_nack(delivery_info.delivery_tag, false, true)
         | 
| 717 717 | 
             
                #   end
         | 
| 718 718 | 
             
                #
         | 
| 719 719 | 
             
                # @example Reject a message
         | 
| @@ -723,7 +723,7 @@ module Bunny | |
| 723 723 | 
             
                #   ch    = conn.create_channel
         | 
| 724 724 | 
             
                #   q.subscribe do |delivery_info, properties, payload|
         | 
| 725 725 | 
             
                #     # requeue the message
         | 
| 726 | 
            -
                #     ch.basic_nack(delivery_info.delivery_tag | 
| 726 | 
            +
                #     ch.basic_nack(delivery_info.delivery_tag)
         | 
| 727 727 | 
             
                #   end
         | 
| 728 728 | 
             
                #
         | 
| 729 729 | 
             
                # @example Requeue a message fetched via basic.get
         | 
| @@ -733,7 +733,7 @@ module Bunny | |
| 733 733 | 
             
                #   ch    = conn.create_channel
         | 
| 734 734 | 
             
                #   # we assume the queue exists and has messages
         | 
| 735 735 | 
             
                #   delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
         | 
| 736 | 
            -
                #   ch.basic_nack(delivery_info.delivery_tag, true)
         | 
| 736 | 
            +
                #   ch.basic_nack(delivery_info.delivery_tag, false, true)
         | 
| 737 737 | 
             
                #
         | 
| 738 738 | 
             
                #
         | 
| 739 739 | 
             
                # @example Requeue multiple messages fetched via basic.get
         | 
| @@ -751,12 +751,12 @@ module Bunny | |
| 751 751 | 
             
                # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
         | 
| 752 752 | 
             
                # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
         | 
| 753 753 | 
             
                # @api public
         | 
| 754 | 
            -
                def basic_nack(delivery_tag,  | 
| 754 | 
            +
                def basic_nack(delivery_tag, multiple = false, requeue = false)
         | 
| 755 755 | 
             
                  raise_if_no_longer_open!
         | 
| 756 756 | 
             
                  @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
         | 
| 757 757 | 
             
                                                                           delivery_tag,
         | 
| 758 | 
            -
                                                                            | 
| 759 | 
            -
                                                                            | 
| 758 | 
            +
                                                                           multiple,
         | 
| 759 | 
            +
                                                                           requeue))
         | 
| 760 760 |  | 
| 761 761 | 
             
                  nil
         | 
| 762 762 | 
             
                end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            require "thread"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Bunny
         | 
| 4 | 
            +
              module Concurrent
         | 
| 5 | 
            +
                class ContinuationQueue
         | 
| 6 | 
            +
                  def initialize(*args, &block)
         | 
| 7 | 
            +
                    @q = ::Queue.new(*args)
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def push(*args)
         | 
| 11 | 
            +
                    @q.push(*args)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                  alias << push
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def pop
         | 
| 16 | 
            +
                    @q.pop
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def clear
         | 
| 20 | 
            +
                    @q.clear
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def method_missing(selector, *args, &block)
         | 
| 24 | 
            +
                    @q.__send__(selector, *args, &block)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            if !defined?(JRUBY_VERSION)
         | 
| 2 | 
            +
              raise "Bunny::Concurrent::LinkedContinuationQueue can only be used on JRuby!"
         | 
| 3 | 
            +
            end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require "java"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            java_import java.util.concurrent.LinkedBlockingQueue
         | 
| 8 | 
            +
            java_import java.util.concurrent.TimeUnit
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module Bunny
         | 
| 11 | 
            +
              module Concurrent
         | 
| 12 | 
            +
                # On JRuby, we'd rather use reliable and heavily battle tested j.u.c.
         | 
| 13 | 
            +
                # primitives with well described semantics than informally specified, clumsy
         | 
| 14 | 
            +
                # and limited Ruby standard library parts.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # This is an implementation of the continuation queue on top of the linked blocking
         | 
| 17 | 
            +
                # queue in j.u.c.
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # Compared to the Ruby standard library Queue, there is one limitation: you cannot
         | 
| 20 | 
            +
                # push a nil on the queue, it will fail with a null pointer exception.
         | 
| 21 | 
            +
                class LinkedContinuationQueue
         | 
| 22 | 
            +
                  def initialize(*args, &block)
         | 
| 23 | 
            +
                    @q = LinkedBlockingQueue.new
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def push(el, timeout_in_ms = nil)
         | 
| 27 | 
            +
                    if timeout_in_ms
         | 
| 28 | 
            +
                      @q.offer(el, timeout_in_ms, TimeUnit.MILLISECONDS)
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      @q.offer(el)
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                  alias << push
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def pop
         | 
| 36 | 
            +
                    @q.take
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def poll(timeout_in_ms = nil)
         | 
| 40 | 
            +
                    if timeout_in_ms
         | 
| 41 | 
            +
                      @q.poll(timeout_in_ms, TimeUnit.MILLISECONDS)
         | 
| 42 | 
            +
                    else
         | 
| 43 | 
            +
                      @q.poll
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def clear
         | 
| 48 | 
            +
                    @q.clear
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def method_missing(selector, *args, &block)
         | 
| 52 | 
            +
                    @q.__send__(selector, *args, &block)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        data/lib/bunny/exceptions.rb
    CHANGED
    
    
    
        data/lib/bunny/session.rb
    CHANGED
    
    | @@ -9,7 +9,11 @@ require "bunny/authentication/credentials_encoder" | |
| 9 9 | 
             
            require "bunny/authentication/plain_mechanism_encoder"
         | 
| 10 10 | 
             
            require "bunny/authentication/external_mechanism_encoder"
         | 
| 11 11 |  | 
| 12 | 
            -
             | 
| 12 | 
            +
            if defined?(JRUBY_VERSION)
         | 
| 13 | 
            +
              require "bunny/concurrent/linked_continuation_queue"
         | 
| 14 | 
            +
            else
         | 
| 15 | 
            +
              require "bunny/concurrent/continuation_queue"
         | 
| 16 | 
            +
            end
         | 
| 13 17 |  | 
| 14 18 | 
             
            require "amq/protocol/client"
         | 
| 15 19 | 
             
            require "amq/settings"
         | 
| @@ -28,7 +32,7 @@ module Bunny | |
| 28 32 | 
             
                # Default password used for connection
         | 
| 29 33 | 
             
                DEFAULT_PASSWORD  = "guest"
         | 
| 30 34 | 
             
                # Default heartbeat interval, the same value as RabbitMQ 3.0 uses.
         | 
| 31 | 
            -
                DEFAULT_HEARTBEAT =  | 
| 35 | 
            +
                DEFAULT_HEARTBEAT = :server
         | 
| 32 36 | 
             
                # @private
         | 
| 33 37 | 
             
                DEFAULT_FRAME_MAX = 131072
         | 
| 34 38 |  | 
| @@ -52,6 +56,8 @@ module Bunny | |
| 52 56 |  | 
| 53 57 | 
             
                DEFAULT_LOCALE = "en_GB"
         | 
| 54 58 |  | 
| 59 | 
            +
                DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
         | 
| 60 | 
            +
             | 
| 55 61 |  | 
| 56 62 | 
             
                #
         | 
| 57 63 | 
             
                # API
         | 
| @@ -107,6 +113,7 @@ module Bunny | |
| 107 113 | 
             
                                           else
         | 
| 108 114 | 
             
                                             opts[:automatically_recover] || opts[:automatic_recovery]
         | 
| 109 115 | 
             
                                           end
         | 
| 116 | 
            +
                  @network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
         | 
| 110 117 |  | 
| 111 118 | 
             
                  @status             = :not_connected
         | 
| 112 119 |  | 
| @@ -124,7 +131,7 @@ module Bunny | |
| 124 131 | 
             
                  @network_mutex       = Mutex.new
         | 
| 125 132 | 
             
                  @channels            = Hash.new
         | 
| 126 133 |  | 
| 127 | 
            -
                   | 
| 134 | 
            +
                  self.reset_continuations
         | 
| 128 135 | 
             
                end
         | 
| 129 136 |  | 
| 130 137 | 
             
                # @return [String] RabbitMQ hostname (or IP address) used
         | 
| @@ -136,6 +143,9 @@ module Bunny | |
| 136 143 | 
             
                # @return [String] Virtual host used
         | 
| 137 144 | 
             
                def virtual_host; self.vhost; end
         | 
| 138 145 |  | 
| 146 | 
            +
                # @return [Integer] Heartbeat interval used
         | 
| 147 | 
            +
                def heartbeat_interval; self.heartbeat; end
         | 
| 148 | 
            +
             | 
| 139 149 | 
             
                # @return [Boolean] true if this connection uses TLS (SSL)
         | 
| 140 150 | 
             
                def uses_tls?
         | 
| 141 151 | 
             
                  @transport.uses_tls?
         | 
| @@ -148,11 +158,18 @@ module Bunny | |
| 148 158 | 
             
                end
         | 
| 149 159 | 
             
                alias ssl? uses_ssl?
         | 
| 150 160 |  | 
| 161 | 
            +
                # @return [Boolean] true if this connection uses a separate thread for I/O activity
         | 
| 162 | 
            +
                def threaded?
         | 
| 163 | 
            +
                  @threaded
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 151 166 | 
             
                # Starts connection process
         | 
| 152 167 | 
             
                # @api public
         | 
| 153 168 | 
             
                def start
         | 
| 154 | 
            -
                   | 
| 169 | 
            +
                  return self if connected?
         | 
| 170 | 
            +
             | 
| 155 171 | 
             
                  @status        = :connecting
         | 
| 172 | 
            +
                  self.reset_continuations
         | 
| 156 173 |  | 
| 157 174 | 
             
                  self.initialize_transport
         | 
| 158 175 |  | 
| @@ -163,6 +180,8 @@ module Bunny | |
| 163 180 | 
             
                  self.start_main_loop if @threaded
         | 
| 164 181 |  | 
| 165 182 | 
             
                  @default_channel = self.create_channel
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  self
         | 
| 166 185 | 
             
                end
         | 
| 167 186 |  | 
| 168 187 | 
             
                def read_write_timeout
         | 
| @@ -197,6 +216,10 @@ module Bunny | |
| 197 216 | 
             
                end
         | 
| 198 217 | 
             
                alias stop close
         | 
| 199 218 |  | 
| 219 | 
            +
                # Creates a temporary channel, yields it to the block given to this
         | 
| 220 | 
            +
                # method and closes it.
         | 
| 221 | 
            +
                #
         | 
| 222 | 
            +
                # @return [Bunny::Session] self
         | 
| 200 223 | 
             
                def with_channel(n = nil)
         | 
| 201 224 | 
             
                  ch = create_channel(n)
         | 
| 202 225 | 
             
                  yield ch
         | 
| @@ -205,7 +228,7 @@ module Bunny | |
| 205 228 | 
             
                  self
         | 
| 206 229 | 
             
                end
         | 
| 207 230 |  | 
| 208 | 
            -
             | 
| 231 | 
            +
                # @return [Boolean] true if this connection is still not fully open
         | 
| 209 232 | 
             
                def connecting?
         | 
| 210 233 | 
             
                  status == :connecting
         | 
| 211 234 | 
             
                end
         | 
| @@ -215,10 +238,11 @@ module Bunny | |
| 215 238 | 
             
                end
         | 
| 216 239 |  | 
| 217 240 | 
             
                def open?
         | 
| 218 | 
            -
                  (status == :open || status == :connected) && @transport.open?
         | 
| 241 | 
            +
                  (status == :open || status == :connected || status == :connecting) && @transport.open?
         | 
| 219 242 | 
             
                end
         | 
| 220 243 | 
             
                alias connected? open?
         | 
| 221 244 |  | 
| 245 | 
            +
                # @return [Boolean] true if this connection has automatic recovery from network failure enabled
         | 
| 222 246 | 
             
                def automatically_recover?
         | 
| 223 247 | 
             
                  @automatically_recover
         | 
| 224 248 | 
             
                end
         | 
| @@ -318,6 +342,8 @@ module Bunny | |
| 318 342 | 
             
                  when AMQ::Protocol::Connection::Close then
         | 
| 319 343 | 
             
                    @last_connection_error = instantiate_connection_level_exception(method)
         | 
| 320 344 | 
             
                    @continuations.push(method)
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                    raise @last_connection_error
         | 
| 321 347 | 
             
                  when AMQ::Protocol::Connection::CloseOk then
         | 
| 322 348 | 
             
                    @last_connection_close_ok = method
         | 
| 323 349 | 
             
                    begin
         | 
| @@ -332,7 +358,7 @@ module Bunny | |
| 332 358 | 
             
                      puts e.message
         | 
| 333 359 | 
             
                      puts e.backtrace
         | 
| 334 360 | 
             
                    ensure
         | 
| 335 | 
            -
                      @continuations.push( | 
| 361 | 
            +
                      @continuations.push(:__unblock__)
         | 
| 336 362 | 
             
                    end
         | 
| 337 363 | 
             
                  when AMQ::Protocol::Channel::Close then
         | 
| 338 364 | 
             
                    begin
         | 
| @@ -376,6 +402,8 @@ module Bunny | |
| 376 402 | 
             
                def handle_network_failure(exception)
         | 
| 377 403 | 
             
                  raise NetworkErrorWrapper.new(exception) unless @threaded
         | 
| 378 404 |  | 
| 405 | 
            +
                  @status = :disconnected
         | 
| 406 | 
            +
             | 
| 379 407 | 
             
                  if !recovering_from_network_failure?
         | 
| 380 408 | 
             
                    @recovering_from_network_failure = true
         | 
| 381 409 | 
             
                    if recoverable_network_failure?(exception)
         | 
| @@ -406,6 +434,7 @@ module Bunny | |
| 406 434 | 
             
                # @private
         | 
| 407 435 | 
             
                def recover_from_network_failure
         | 
| 408 436 | 
             
                  begin
         | 
| 437 | 
            +
                    sleep @network_recovery_interval
         | 
| 409 438 | 
             
                    # puts "About to start recovery..."
         | 
| 410 439 | 
             
                    start
         | 
| 411 440 |  | 
| @@ -416,7 +445,7 @@ module Bunny | |
| 416 445 | 
             
                    end
         | 
| 417 446 | 
             
                  rescue TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e
         | 
| 418 447 | 
             
                    # puts "TCP connection failed, reconnecting in 5 seconds"
         | 
| 419 | 
            -
                    sleep  | 
| 448 | 
            +
                    sleep @network_recovery_interval
         | 
| 420 449 | 
             
                    retry if recoverable_network_failure?(e)
         | 
| 421 450 | 
             
                  end
         | 
| 422 451 | 
             
                end
         | 
| @@ -443,11 +472,13 @@ module Bunny | |
| 443 472 | 
             
                  case frame
         | 
| 444 473 | 
             
                  when AMQ::Protocol::Connection::Close then
         | 
| 445 474 | 
             
                    klass = case frame.reply_code
         | 
| 475 | 
            +
                            when 320 then
         | 
| 476 | 
            +
                              ConnectionForced
         | 
| 446 477 | 
             
                            when 503 then
         | 
| 447 478 | 
             
                              InvalidCommand
         | 
| 448 479 | 
             
                            when 504 then
         | 
| 449 480 | 
             
                              ChannelError
         | 
| 450 | 
            -
                            when  | 
| 481 | 
            +
                            when 505 then
         | 
| 451 482 | 
             
                              UnexpectedFrame
         | 
| 452 483 | 
             
                            else
         | 
| 453 484 | 
             
                              raise "Unknown reply code: #{frame.reply_code}, text: #{frame.reply_text}"
         | 
| @@ -530,6 +561,11 @@ module Bunny | |
| 530 561 | 
             
                  @event_loop ||= MainLoop.new(@transport, self, Thread.current)
         | 
| 531 562 | 
             
                end
         | 
| 532 563 |  | 
| 564 | 
            +
                # @private
         | 
| 565 | 
            +
                def maybe_shutdown_main_loop
         | 
| 566 | 
            +
                  @event_loop.stop if @event_loop
         | 
| 567 | 
            +
                end
         | 
| 568 | 
            +
             | 
| 533 569 | 
             
                # @private
         | 
| 534 570 | 
             
                def signal_activity!
         | 
| 535 571 | 
             
                  @heartbeat_sender.signal_activity! if @heartbeat_sender
         | 
| @@ -633,7 +669,7 @@ module Bunny | |
| 633 669 | 
             
                  @frame_max            = negotiate_value(@client_frame_max, connection_tune.frame_max)
         | 
| 634 670 | 
             
                  @channel_max          = negotiate_value(@client_channel_max, connection_tune.channel_max)
         | 
| 635 671 | 
             
                  # this allows for disabled heartbeats. MK.
         | 
| 636 | 
            -
                  @heartbeat            = if  | 
| 672 | 
            +
                  @heartbeat            = if heartbeat_disabled?(@client_heartbeat)
         | 
| 637 673 | 
             
                                            0
         | 
| 638 674 | 
             
                                          else
         | 
| 639 675 | 
             
                                            negotiate_value(@client_heartbeat, connection_tune.heartbeat)
         | 
| @@ -665,8 +701,14 @@ module Bunny | |
| 665 701 | 
             
                  raise "could not open connection: server did not respond with connection.open-ok" unless connection_open_ok.is_a?(AMQ::Protocol::Connection::OpenOk)
         | 
| 666 702 | 
             
                end
         | 
| 667 703 |  | 
| 704 | 
            +
                def heartbeat_disabled?(val)
         | 
| 705 | 
            +
                  0 == val || val.nil?
         | 
| 706 | 
            +
                end
         | 
| 707 | 
            +
             | 
| 668 708 | 
             
                # @api private
         | 
| 669 709 | 
             
                def negotiate_value(client_value, server_value)
         | 
| 710 | 
            +
                  return server_value if client_value == :server
         | 
| 711 | 
            +
             | 
| 670 712 | 
             
                  if client_value == 0 || server_value == 0
         | 
| 671 713 | 
             
                    [client_value, server_value].max
         | 
| 672 714 | 
             
                  else
         | 
| @@ -708,6 +750,15 @@ module Bunny | |
| 708 750 | 
             
                  Authentication::CredentialsEncoder.for_session(self)
         | 
| 709 751 | 
             
                end
         | 
| 710 752 |  | 
| 753 | 
            +
                # @api private
         | 
| 754 | 
            +
                def reset_continuations
         | 
| 755 | 
            +
                  @continuations = if defined?(JRUBY_VERSION)
         | 
| 756 | 
            +
                                     Concurrent::LinkedContinuationQueue.new
         | 
| 757 | 
            +
                                   else
         | 
| 758 | 
            +
                                     Concurrent::ContinuationQueue.new
         | 
| 759 | 
            +
                                   end
         | 
| 760 | 
            +
                end
         | 
| 761 | 
            +
             | 
| 711 762 | 
             
                # @api private
         | 
| 712 763 | 
             
                def wait_on_continuations
         | 
| 713 764 | 
             
                  unless @threaded
         | 
    
        data/lib/bunny/socket.rb
    CHANGED
    
    | @@ -14,7 +14,7 @@ module Bunny | |
| 14 14 | 
             
                    if Socket.constants.include?('TCP_NODELAY') || Socket.constants.include?(:TCP_NODELAY)
         | 
| 15 15 | 
             
                      sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
         | 
| 16 16 | 
             
                    end
         | 
| 17 | 
            -
                    sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options | 
| 17 | 
            +
                    sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
         | 
| 18 18 | 
             
                    sock.options = {:host => host, :port => port}.merge(options)
         | 
| 19 19 | 
             
                    sock
         | 
| 20 20 | 
             
                  end
         | 
    
        data/lib/bunny/version.rb
    CHANGED
    
    
| @@ -14,6 +14,7 @@ describe "Publishing a message to the default exchange" do | |
| 14 14 |  | 
| 15 15 | 
             
              context "with all default delivery and message properties" do
         | 
| 16 16 | 
             
                it "routes messages to a queue with the same name as the routing key" do
         | 
| 17 | 
            +
                  connection.should be_threaded
         | 
| 17 18 | 
             
                  ch = connection.create_channel
         | 
| 18 19 |  | 
| 19 20 | 
             
                  q  = ch.queue("", :exclusive => true)
         | 
| @@ -34,6 +35,7 @@ describe "Publishing a message to the default exchange" do | |
| 34 35 |  | 
| 35 36 | 
             
              context "with all default delivery and message properties" do
         | 
| 36 37 | 
             
                it "routes the messages and preserves all the metadata" do
         | 
| 38 | 
            +
                  connection.should be_threaded
         | 
| 37 39 | 
             
                  ch = connection.create_channel
         | 
| 38 40 |  | 
| 39 41 | 
             
                  q  = ch.queue("", :exclusive => true)
         | 
| @@ -55,4 +57,31 @@ describe "Publishing a message to the default exchange" do | |
| 55 57 | 
             
                  ch.close
         | 
| 56 58 | 
             
                end
         | 
| 57 59 | 
             
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
             | 
| 62 | 
            +
              context "with all default delivery and message properties on a single-threaded connection" do
         | 
| 63 | 
            +
                let(:connection) do
         | 
| 64 | 
            +
                  c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :threaded => false)
         | 
| 65 | 
            +
                  c.start
         | 
| 66 | 
            +
                  c
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                it "routes messages to a queue with the same name as the routing key" do
         | 
| 70 | 
            +
                  connection.should_not be_threaded
         | 
| 71 | 
            +
                  ch = connection.create_channel
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  q  = ch.queue("", :exclusive => true)
         | 
| 74 | 
            +
                  x  = ch.default_exchange
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  x.publish("xyzzy", :routing_key => q.name).
         | 
| 77 | 
            +
                    publish("xyzzy", :routing_key => q.name).
         | 
| 78 | 
            +
                    publish("xyzzy", :routing_key => q.name).
         | 
| 79 | 
            +
                    publish("xyzzy", :routing_key => q.name)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  sleep(1)
         | 
| 82 | 
            +
                  q.message_count.should == 4
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  ch.close
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 58 87 | 
             
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "Client-defined heartbeat interval" do
         | 
| 4 | 
            +
              let(:connection) do
         | 
| 5 | 
            +
                c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :heartbeat_interval => 4)
         | 
| 6 | 
            +
                c.start
         | 
| 7 | 
            +
                c
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              it "can be enabled explicitly" do
         | 
| 11 | 
            +
                sleep 5.0
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                connection.close
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
             | 
| 18 | 
            +
            describe "Server-defined heartbeat interval" do
         | 
| 19 | 
            +
              let(:connection) do
         | 
| 20 | 
            +
                c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :heartbeat_interval => :server)
         | 
| 21 | 
            +
                c.start
         | 
| 22 | 
            +
                c
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              it "can be enabled explicitly" do
         | 
| 26 | 
            +
                puts "Sleeping for 5 seconds with heartbeat interval of 4"
         | 
| 27 | 
            +
                sleep 5.0
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                connection.close
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "Rapidly opening and closing lots of channels on a non-threaded connection" do
         | 
| 4 | 
            +
              let(:connection) do
         | 
| 5 | 
            +
                c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :automatic_recovery => false, :threaded => false)
         | 
| 6 | 
            +
                c.start
         | 
| 7 | 
            +
                c
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              after :all do
         | 
| 11 | 
            +
                connection.close
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              context "in a single-threaded scenario" do
         | 
| 15 | 
            +
                let(:n) { 500 }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                it "works correctly" do
         | 
| 18 | 
            +
                  xs = Array.new(n) { connection.create_channel }
         | 
| 19 | 
            +
                  puts "Opened #{n} channels"
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  xs.size.should == n
         | 
| 22 | 
            +
                  xs.each do |ch|
         | 
| 23 | 
            +
                    ch.close
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              context "in a multi-threaded scenario" do
         | 
| 29 | 
            +
                # actually, on MRI values greater than ~100 will eventually cause write
         | 
| 30 | 
            +
                # operations to fail with a timeout (1 second is not enough)
         | 
| 31 | 
            +
                # which will cause recovery to re-acquire @channel_mutex in Session.
         | 
| 32 | 
            +
                # Because Ruby's mutexes are not re-entrant, it will raise a ThreadError.
         | 
| 33 | 
            +
                #
         | 
| 34 | 
            +
                # But this already demonstrates that within these platform constraints,
         | 
| 35 | 
            +
                # Bunny is safe to use in such scenarios.
         | 
| 36 | 
            +
                let(:n) { 50 }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it "works correctly" do
         | 
| 39 | 
            +
                  n.times do
         | 
| 40 | 
            +
                    t = Thread.new do
         | 
| 41 | 
            +
                      ch = connection.create_channel
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      ch.close
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                    t.abort_on_exception = true
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: bunny
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.9.0. | 
| 4 | 
            +
              version: 0.9.0.pre9
         | 
| 5 5 | 
             
              prerelease: 6
         | 
| 6 6 | 
             
            platform: ruby
         | 
| 7 7 | 
             
            authors:
         | 
| @@ -13,7 +13,7 @@ authors: | |
| 13 13 | 
             
            autorequire:
         | 
| 14 14 | 
             
            bindir: bin
         | 
| 15 15 | 
             
            cert_chain: []
         | 
| 16 | 
            -
            date: 2013- | 
| 16 | 
            +
            date: 2013-04-22 00:00:00.000000000 Z
         | 
| 17 17 | 
             
            dependencies:
         | 
| 18 18 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 19 19 | 
             
              name: amq-protocol
         | 
| @@ -93,6 +93,8 @@ files: | |
| 93 93 | 
             
            - lib/bunny/channel_id_allocator.rb
         | 
| 94 94 | 
             
            - lib/bunny/compatibility.rb
         | 
| 95 95 | 
             
            - lib/bunny/concurrent/condition.rb
         | 
| 96 | 
            +
            - lib/bunny/concurrent/continuation_queue.rb
         | 
| 97 | 
            +
            - lib/bunny/concurrent/linked_continuation_queue.rb
         | 
| 96 98 | 
             
            - lib/bunny/consumer.rb
         | 
| 97 99 | 
             
            - lib/bunny/consumer_tag_generator.rb
         | 
| 98 100 | 
             
            - lib/bunny/consumer_work_pool.rb
         | 
| @@ -134,6 +136,7 @@ files: | |
| 134 136 | 
             
            - spec/higher_level_api/integration/exchange_declare_spec.rb
         | 
| 135 137 | 
             
            - spec/higher_level_api/integration/exchange_delete_spec.rb
         | 
| 136 138 | 
             
            - spec/higher_level_api/integration/exchange_unbind_spec.rb
         | 
| 139 | 
            +
            - spec/higher_level_api/integration/heartbeat_spec.rb
         | 
| 137 140 | 
             
            - spec/higher_level_api/integration/message_properties_access_spec.rb
         | 
| 138 141 | 
             
            - spec/higher_level_api/integration/predeclared_exchanges_spec.rb
         | 
| 139 142 | 
             
            - spec/higher_level_api/integration/publisher_confirms_spec.rb
         | 
| @@ -155,6 +158,7 @@ files: | |
| 155 158 | 
             
            - spec/lower_level_api/integration/basic_consume_spec.rb
         | 
| 156 159 | 
             
            - spec/spec_helper.rb
         | 
| 157 160 | 
             
            - spec/stress/channel_open_stress_spec.rb
         | 
| 161 | 
            +
            - spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb
         | 
| 158 162 | 
             
            - spec/stress/concurrent_consumers_stress_spec.rb
         | 
| 159 163 | 
             
            - spec/stress/concurrent_publishers_stress_spec.rb
         | 
| 160 164 | 
             
            - spec/unit/bunny_spec.rb
         | 
| @@ -211,6 +215,7 @@ test_files: | |
| 211 215 | 
             
            - spec/higher_level_api/integration/exchange_declare_spec.rb
         | 
| 212 216 | 
             
            - spec/higher_level_api/integration/exchange_delete_spec.rb
         | 
| 213 217 | 
             
            - spec/higher_level_api/integration/exchange_unbind_spec.rb
         | 
| 218 | 
            +
            - spec/higher_level_api/integration/heartbeat_spec.rb
         | 
| 214 219 | 
             
            - spec/higher_level_api/integration/message_properties_access_spec.rb
         | 
| 215 220 | 
             
            - spec/higher_level_api/integration/predeclared_exchanges_spec.rb
         | 
| 216 221 | 
             
            - spec/higher_level_api/integration/publisher_confirms_spec.rb
         | 
| @@ -232,6 +237,7 @@ test_files: | |
| 232 237 | 
             
            - spec/lower_level_api/integration/basic_consume_spec.rb
         | 
| 233 238 | 
             
            - spec/spec_helper.rb
         | 
| 234 239 | 
             
            - spec/stress/channel_open_stress_spec.rb
         | 
| 240 | 
            +
            - spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb
         | 
| 235 241 | 
             
            - spec/stress/concurrent_consumers_stress_spec.rb
         | 
| 236 242 | 
             
            - spec/stress/concurrent_publishers_stress_spec.rb
         | 
| 237 243 | 
             
            - spec/unit/bunny_spec.rb
         |