amqp-client 1.2.1 → 2.0.0
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.
- checksums.yaml +4 -4
- data/lib/amqp/client/channel.rb +115 -46
- data/lib/amqp/client/configuration.rb +66 -0
- data/lib/amqp/client/connection.rb +35 -18
- data/lib/amqp/client/consumer.rb +47 -0
- data/lib/amqp/client/errors.rb +84 -9
- data/lib/amqp/client/exchange.rb +24 -26
- data/lib/amqp/client/frame_bytes.rb +3 -4
- data/lib/amqp/client/message.rb +66 -1
- data/lib/amqp/client/message_codec_registry.rb +106 -0
- data/lib/amqp/client/message_codecs.rb +43 -0
- data/lib/amqp/client/properties.rb +16 -15
- data/lib/amqp/client/queue.rb +48 -14
- data/lib/amqp/client/rpc_client.rb +56 -0
- data/lib/amqp/client/table.rb +2 -2
- data/lib/amqp/client/version.rb +1 -1
- data/lib/amqp/client.rb +343 -79
- metadata +8 -18
- data/.github/workflows/codeql-analysis.yml +0 -41
- data/.github/workflows/docs.yml +0 -28
- data/.github/workflows/main.yml +0 -147
- data/.github/workflows/release.yml +0 -54
- data/.gitignore +0 -9
- data/.rubocop.yml +0 -30
- data/.rubocop_todo.yml +0 -65
- data/.yardopts +0 -1
- data/CHANGELOG.md +0 -115
- data/CODEOWNERS +0 -1
- data/Gemfile +0 -18
- data/README.md +0 -193
- data/Rakefile +0 -197
- data/amqp-client.gemspec +0 -29
- data/bin/console +0 -15
- data/bin/setup +0 -8
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c07d38a543ff2c0e6e57dfc4ce4921ba74dbac1091cd55a7d06fc791a4ba501e
         | 
| 4 | 
            +
              data.tar.gz: 1909a126c2c6c21d36990cff7b0c623ad4e843afe52ddd5cad69299487286f0c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6628d1283167e699482baf15ae84bc336c59015b9a86921090ea2a15513267e1fe290788640cef65f92e88f779b039288b05fa6a50d25a449144268edeac7003
         | 
| 7 | 
            +
              data.tar.gz: 1684f2e5ef0ffb7c8a67371fe80031904681ccac21e164d190a87605f52b5b73560b4dbd2862f4047f944238f1126aa6de139cdbd1fa1b9acdd39324f8d71c56
         | 
    
        data/lib/amqp/client/channel.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative " | 
| 3 | 
            +
            require_relative "message"
         | 
| 4 4 | 
             
            require "stringio"
         | 
| 5 5 |  | 
| 6 6 | 
             
            module AMQP
         | 
| @@ -25,20 +25,25 @@ module AMQP | |
| 25 25 | 
             
                      @unconfirmed = []
         | 
| 26 26 | 
             
                      @unconfirmed_lock = Mutex.new
         | 
| 27 27 | 
             
                      @unconfirmed_empty = ConditionVariable.new
         | 
| 28 | 
            +
                      @nacked = false
         | 
| 28 29 | 
             
                      @basic_gets = ::Queue.new
         | 
| 29 30 | 
             
                    end
         | 
| 30 31 |  | 
| 31 32 | 
             
                    # Override #inspect
         | 
| 32 33 | 
             
                    # @api private
         | 
| 33 34 | 
             
                    def inspect
         | 
| 34 | 
            -
                      "#<#{self.class} @id=#{@id} @open=#{@open} @closed=#{@closed} confirm_selected=#{!@confirm.nil?}"\
         | 
| 35 | 
            -
                        " | 
| 35 | 
            +
                      "#<#{self.class} @id=#{@id} @open=#{@open} @closed=#{@closed} confirm_selected=#{!@confirm.nil?} " \
         | 
| 36 | 
            +
                        "consumer_count=#{@consumers.size} replies_count=#{@replies.size} unconfirmed_count=#{@unconfirmed.size}>"
         | 
| 36 37 | 
             
                    end
         | 
| 37 38 |  | 
| 38 39 | 
             
                    # Channel ID
         | 
| 39 40 | 
             
                    # @return [Integer]
         | 
| 40 41 | 
             
                    attr_reader :id
         | 
| 41 42 |  | 
| 43 | 
            +
                    # Connection this channel belongs to
         | 
| 44 | 
            +
                    # @return [Connection]
         | 
| 45 | 
            +
                    attr_reader :connection
         | 
| 46 | 
            +
             | 
| 42 47 | 
             
                    # Open the channel (called from Connection)
         | 
| 43 48 | 
             
                    # @return [Channel] self
         | 
| 44 49 | 
             
                    # @api private
         | 
| @@ -51,7 +56,9 @@ module AMQP | |
| 51 56 | 
             
                      self
         | 
| 52 57 | 
             
                    end
         | 
| 53 58 |  | 
| 54 | 
            -
                    # Gracefully close  | 
| 59 | 
            +
                    # Gracefully close channel
         | 
| 60 | 
            +
                    # @param reason [String] The reason for closing the channel
         | 
| 61 | 
            +
                    # @param code [Integer] The close code
         | 
| 55 62 | 
             
                    # @return [nil]
         | 
| 56 63 | 
             
                    def close(reason: "", code: 200)
         | 
| 57 64 | 
             
                      return if @closed
         | 
| @@ -62,7 +69,7 @@ module AMQP | |
| 62 69 | 
             
                      @replies.close
         | 
| 63 70 | 
             
                      @basic_gets.close
         | 
| 64 71 | 
             
                      @unconfirmed_lock.synchronize { @unconfirmed_empty.broadcast }
         | 
| 65 | 
            -
                      @consumers.each_value( | 
| 72 | 
            +
                      @consumers.each_value { |c| close_consumer(c) }
         | 
| 66 73 | 
             
                      nil
         | 
| 67 74 | 
             
                    end
         | 
| 68 75 |  | 
| @@ -75,8 +82,10 @@ module AMQP | |
| 75 82 | 
             
                      @replies.close
         | 
| 76 83 | 
             
                      @basic_gets.close
         | 
| 77 84 | 
             
                      @unconfirmed_lock.synchronize { @unconfirmed_empty.broadcast }
         | 
| 78 | 
            -
                      @consumers.each_value | 
| 79 | 
            -
             | 
| 85 | 
            +
                      @consumers.each_value do |c|
         | 
| 86 | 
            +
                        close_consumer(c)
         | 
| 87 | 
            +
                        c.msg_q.clear # empty the queues too, messages can't be acked anymore
         | 
| 88 | 
            +
                      end
         | 
| 80 89 | 
             
                      nil
         | 
| 81 90 | 
             
                    end
         | 
| 82 91 |  | 
| @@ -100,7 +109,7 @@ module AMQP | |
| 100 109 | 
             
                    # @param internal [Boolean] If true the exchange can't be published to directly
         | 
| 101 110 | 
             
                    # @param arguments [Hash] Custom arguments
         | 
| 102 111 | 
             
                    # @return [nil]
         | 
| 103 | 
            -
                    def exchange_declare(name, type | 
| 112 | 
            +
                    def exchange_declare(name, type:, passive: false, durable: true, auto_delete: false, internal: false, arguments: {})
         | 
| 104 113 | 
             
                      write_bytes FrameBytes.exchange_declare(@id, name, type, passive, durable, auto_delete, internal, arguments)
         | 
| 105 114 | 
             
                      expect :exchange_declare_ok
         | 
| 106 115 | 
             
                      nil
         | 
| @@ -118,24 +127,24 @@ module AMQP | |
| 118 127 | 
             
                    end
         | 
| 119 128 |  | 
| 120 129 | 
             
                    # Bind an exchange to another exchange
         | 
| 121 | 
            -
                    # @param  | 
| 122 | 
            -
                    # @param  | 
| 130 | 
            +
                    # @param source [String] Name of the source exchange
         | 
| 131 | 
            +
                    # @param destination [String] Name of the destination exchange
         | 
| 123 132 | 
             
                    # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
         | 
| 124 133 | 
             
                    # @param arguments [Hash] Message headers to match on, but only when bound to header exchanges
         | 
| 125 134 | 
             
                    # @return [nil]
         | 
| 126 | 
            -
                    def exchange_bind(destination | 
| 135 | 
            +
                    def exchange_bind(source:, destination:, binding_key:, arguments: {})
         | 
| 127 136 | 
             
                      write_bytes FrameBytes.exchange_bind(@id, destination, source, binding_key, false, arguments)
         | 
| 128 137 | 
             
                      expect :exchange_bind_ok
         | 
| 129 138 | 
             
                      nil
         | 
| 130 139 | 
             
                    end
         | 
| 131 140 |  | 
| 132 141 | 
             
                    # Unbind an exchange from another exchange
         | 
| 133 | 
            -
                    # @param  | 
| 134 | 
            -
                    # @param  | 
| 142 | 
            +
                    # @param source [String] Name of the source exchange
         | 
| 143 | 
            +
                    # @param destination [String] Name of the destination exchange
         | 
| 135 144 | 
             
                    # @param binding_key [String] Binding key which the queue is bound to the exchange with
         | 
| 136 145 | 
             
                    # @param arguments [Hash] Arguments matching the binding that's being removed
         | 
| 137 146 | 
             
                    # @return [nil]
         | 
| 138 | 
            -
                    def exchange_unbind(destination | 
| 147 | 
            +
                    def exchange_unbind(source:, destination:, binding_key:, arguments: {})
         | 
| 139 148 | 
             
                      write_bytes FrameBytes.exchange_unbind(@id, destination, source, binding_key, false, arguments)
         | 
| 140 149 | 
             
                      expect :exchange_unbind_ok
         | 
| 141 150 | 
             
                      nil
         | 
| @@ -151,18 +160,18 @@ module AMQP | |
| 151 160 | 
             
                    #   @return [Integer] Number of messages in the queue at the time of declaration
         | 
| 152 161 | 
             
                    # @!attribute consumer_count
         | 
| 153 162 | 
             
                    #   @return [Integer] Number of consumers subscribed to the queue at the time of declaration
         | 
| 154 | 
            -
                    QueueOk =  | 
| 163 | 
            +
                    QueueOk = Data.define(:queue_name, :message_count, :consumer_count)
         | 
| 155 164 |  | 
| 156 165 | 
             
                    # Create a queue (operation is idempotent)
         | 
| 157 166 | 
             
                    # @param name [String] Name of the queue, can be empty, but will then be generated by the broker
         | 
| 158 167 | 
             
                    # @param passive [Boolean] If true an exception will be raised if the queue doesn't already exists
         | 
| 159 168 | 
             
                    # @param durable [Boolean] If true the queue will survive broker restarts,
         | 
| 160 169 | 
             
                    #   messages in the queue will only survive if they are published as persistent
         | 
| 161 | 
            -
                    # @param exclusive [Boolean] If true the queue will be deleted when the  | 
| 170 | 
            +
                    # @param exclusive [Boolean] If true the queue will be deleted when the connection is closed
         | 
| 162 171 | 
             
                    # @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
         | 
| 163 172 | 
             
                    #   (it won't be deleted until at least one consumer has consumed from it)
         | 
| 164 173 | 
             
                    # @param arguments [Hash] Custom arguments, such as queue-ttl etc.
         | 
| 165 | 
            -
                    # @return [QueueOk] | 
| 174 | 
            +
                    # @return [QueueOk]
         | 
| 166 175 | 
             
                    def queue_declare(name = "", passive: false, durable: true, exclusive: false, auto_delete: false, arguments: {})
         | 
| 167 176 | 
             
                      durable = false if name.empty?
         | 
| 168 177 | 
             
                      exclusive = true if name.empty?
         | 
| @@ -188,12 +197,12 @@ module AMQP | |
| 188 197 | 
             
                    end
         | 
| 189 198 |  | 
| 190 199 | 
             
                    # Bind a queue to an exchange
         | 
| 191 | 
            -
                    # @param name [String] Name of the queue | 
| 192 | 
            -
                    # @param exchange [String] Name of the exchange | 
| 200 | 
            +
                    # @param name [String] Name of the queue
         | 
| 201 | 
            +
                    # @param exchange [String] Name of the exchange
         | 
| 193 202 | 
             
                    # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
         | 
| 194 203 | 
             
                    # @param arguments [Hash] Message headers to match on, but only when bound to header exchanges
         | 
| 195 204 | 
             
                    # @return [nil]
         | 
| 196 | 
            -
                    def queue_bind(name, exchange | 
| 205 | 
            +
                    def queue_bind(name, exchange:, binding_key: "", arguments: {})
         | 
| 197 206 | 
             
                      write_bytes FrameBytes.queue_bind(@id, name, exchange, binding_key, false, arguments)
         | 
| 198 207 | 
             
                      expect :queue_bind_ok
         | 
| 199 208 | 
             
                      nil
         | 
| @@ -211,12 +220,12 @@ module AMQP | |
| 211 220 | 
             
                    end
         | 
| 212 221 |  | 
| 213 222 | 
             
                    # Unbind a queue from an exchange
         | 
| 214 | 
            -
                    # @param name [String] Name of the queue | 
| 215 | 
            -
                    # @param exchange [String] Name of the exchange | 
| 223 | 
            +
                    # @param name [String] Name of the queue
         | 
| 224 | 
            +
                    # @param exchange [String] Name of the exchange
         | 
| 216 225 | 
             
                    # @param binding_key [String] Binding key which the queue is bound to the exchange with
         | 
| 217 226 | 
             
                    # @param arguments [Hash] Arguments matching the binding that's being removed
         | 
| 218 227 | 
             
                    # @return [nil]
         | 
| 219 | 
            -
                    def queue_unbind(name, exchange | 
| 228 | 
            +
                    def queue_unbind(name, exchange:, binding_key: "", arguments: {})
         | 
| 220 229 | 
             
                      write_bytes FrameBytes.queue_unbind(@id, name, exchange, binding_key, arguments)
         | 
| 221 230 | 
             
                      expect :queue_unbind_ok
         | 
| 222 231 | 
             
                      nil
         | 
| @@ -240,7 +249,7 @@ module AMQP | |
| 240 249 | 
             
                    end
         | 
| 241 250 |  | 
| 242 251 | 
             
                    # Publishes a message to an exchange
         | 
| 243 | 
            -
                    # @param body [String] The body | 
| 252 | 
            +
                    # @param body [String] The body
         | 
| 244 253 | 
             
                    # @param exchange [String] Name of the exchange to publish to
         | 
| 245 254 | 
             
                    # @param routing_key [String] The routing key that the exchange might use to route the message to a queue
         | 
| 246 255 | 
             
                    # @param properties [Properties]
         | 
| @@ -260,7 +269,7 @@ module AMQP | |
| 260 269 | 
             
                    # @option properties [String] user_id Can be used to verify that this is the user that published the message
         | 
| 261 270 | 
             
                    # @option properties [String] app_id Can be used to indicates which app that generated the message
         | 
| 262 271 | 
             
                    # @return [nil]
         | 
| 263 | 
            -
                    def basic_publish(body, exchange | 
| 272 | 
            +
                    def basic_publish(body, exchange:, routing_key: "", **properties)
         | 
| 264 273 | 
             
                      body_max = @connection.frame_max - 8
         | 
| 265 274 | 
             
                      id = @id
         | 
| 266 275 | 
             
                      mandatory = properties.delete(:mandatory) || false
         | 
| @@ -297,50 +306,90 @@ module AMQP | |
| 297 306 | 
             
                    # @option (see #basic_publish)
         | 
| 298 307 | 
             
                    # @return [Boolean] True if the message was successfully published
         | 
| 299 308 | 
             
                    # @raise (see #basic_publish)
         | 
| 300 | 
            -
                    def basic_publish_confirm(body, exchange | 
| 309 | 
            +
                    def basic_publish_confirm(body, exchange:, routing_key: "", **properties)
         | 
| 301 310 | 
             
                      confirm_select(no_wait: true)
         | 
| 302 | 
            -
                      basic_publish(body, exchange | 
| 311 | 
            +
                      basic_publish(body, exchange:, routing_key:, **properties)
         | 
| 303 312 | 
             
                      wait_for_confirms
         | 
| 304 313 | 
             
                    end
         | 
| 305 314 |  | 
| 315 | 
            +
                    # Response when subscribing (starting a consumer)
         | 
| 316 | 
            +
                    # @!attribute channel_id
         | 
| 317 | 
            +
                    #   @return [Integer] The channel ID
         | 
| 318 | 
            +
                    # @!attribute consumer_tag
         | 
| 319 | 
            +
                    #   @return [String] The consumer tag
         | 
| 320 | 
            +
                    # @!attribute worker_threads
         | 
| 321 | 
            +
                    #   @return [Array<Thread>] Array of worker threads
         | 
| 322 | 
            +
                    ConsumeOk = Data.define(:channel_id, :consumer_tag, :worker_threads, :msg_q, :on_cancel)
         | 
| 323 | 
            +
             | 
| 306 324 | 
             
                    # Consume messages from a queue
         | 
| 307 325 | 
             
                    # @param queue [String] Name of the queue to subscribe to
         | 
| 308 326 | 
             
                    # @param tag [String] Custom consumer tag, will be auto assigned by the broker if empty.
         | 
| 309 | 
            -
                    #   Has to be  | 
| 327 | 
            +
                    #   Has to be unique among this channel's consumers only
         | 
| 310 328 | 
             
                    # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
         | 
| 311 329 | 
             
                    # @param exclusive [Boolean] When true only a single consumer can consume from the queue at a time
         | 
| 312 330 | 
             
                    # @param arguments [Hash] Custom arguments for the consumer
         | 
| 313 331 | 
             
                    # @param worker_threads [Integer] Number of threads processing messages,
         | 
| 314 332 | 
             
                    #   0 means that the thread calling this method will process the messages and thus this method will block
         | 
| 333 | 
            +
                    # @param on_cancel [Proc] Optional proc that will be called if the consumer is cancelled by the broker
         | 
| 334 | 
            +
                    #   The proc will be called with the consumer tag as the only argument
         | 
| 315 335 | 
             
                    # @yield [Message] Delivered message from the queue
         | 
| 316 | 
            -
                    # @return [ | 
| 336 | 
            +
                    # @return [ConsumeOk]
         | 
| 317 337 | 
             
                    # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
         | 
| 318 | 
            -
                    def basic_consume(queue, tag: "", no_ack: true, exclusive: false,  | 
| 319 | 
            -
             | 
| 320 | 
            -
                       | 
| 321 | 
            -
             | 
| 338 | 
            +
                    def basic_consume(queue, tag: "", no_ack: true, exclusive: false, no_wait: false,
         | 
| 339 | 
            +
                                      arguments: {}, worker_threads: 1, on_cancel: nil, &blk)
         | 
| 340 | 
            +
                      raise ArgumentError, "consumer_tag required when no_wait" if no_wait && tag.empty?
         | 
| 341 | 
            +
             | 
| 342 | 
            +
                      write_bytes FrameBytes.basic_consume(@id, queue, tag, no_ack, exclusive, no_wait, arguments)
         | 
| 343 | 
            +
                      consumer_tag, = expect(:basic_consume_ok) unless no_wait
         | 
| 344 | 
            +
                      msg_q = ::Queue.new
         | 
| 322 345 | 
             
                      if worker_threads.zero?
         | 
| 323 | 
            -
                         | 
| 346 | 
            +
                        @consumers[consumer_tag] =
         | 
| 347 | 
            +
                          ConsumeOk.new(channel_id: @id, consumer_tag:, worker_threads: [], msg_q:, on_cancel:)
         | 
| 348 | 
            +
                        consume_loop(msg_q, consumer_tag, &blk)
         | 
| 324 349 | 
             
                        nil
         | 
| 325 350 | 
             
                      else
         | 
| 326 351 | 
             
                        threads = Array.new(worker_threads) do
         | 
| 327 | 
            -
                          Thread.new { consume_loop( | 
| 352 | 
            +
                          Thread.new { consume_loop(msg_q, consumer_tag, &blk) }
         | 
| 328 353 | 
             
                        end
         | 
| 329 | 
            -
                        [ | 
| 354 | 
            +
                        @consumers[consumer_tag] =
         | 
| 355 | 
            +
                          ConsumeOk.new(channel_id: @id, consumer_tag:, worker_threads: threads, msg_q:, on_cancel:)
         | 
| 330 356 | 
             
                      end
         | 
| 331 357 | 
             
                    end
         | 
| 332 358 |  | 
| 359 | 
            +
                    # Consume a single message from a queue
         | 
| 360 | 
            +
                    # @param queue [String] Name of the queue to subscribe to
         | 
| 361 | 
            +
                    # @param timeout [Numeric, nil] Number of seconds to wait for a message
         | 
| 362 | 
            +
                    # @yield [] Block in which the message will be yielded
         | 
| 363 | 
            +
                    # @return [Message] The single message received from the queue
         | 
| 364 | 
            +
                    # @raise [Timeout::Error] if no response is received within the timeout period
         | 
| 365 | 
            +
                    def basic_consume_once(queue, timeout: nil, &)
         | 
| 366 | 
            +
                      tag = "consume-once-#{rand(1024)}"
         | 
| 367 | 
            +
                      write_bytes FrameBytes.basic_consume(@id, queue, tag, true, false, true, nil)
         | 
| 368 | 
            +
                      msg_q = ::Queue.new
         | 
| 369 | 
            +
                      @consumers[tag] =
         | 
| 370 | 
            +
                        ConsumeOk.new(channel_id: @id, consumer_tag: tag, worker_threads: [], msg_q:, on_cancel: nil)
         | 
| 371 | 
            +
                      yield if block_given?
         | 
| 372 | 
            +
                      msg = msg_q.pop(timeout:)
         | 
| 373 | 
            +
                      write_bytes FrameBytes.basic_cancel(@id, tag, no_wait: true)
         | 
| 374 | 
            +
                      consumer = @consumers.delete(tag)
         | 
| 375 | 
            +
                      close_consumer(consumer)
         | 
| 376 | 
            +
                      raise Timeout::Error, "No message received in #{timeout} seconds" if timeout && msg.nil?
         | 
| 377 | 
            +
             | 
| 378 | 
            +
                      msg
         | 
| 379 | 
            +
                    end
         | 
| 380 | 
            +
             | 
| 333 381 | 
             
                    # Cancel/abort/stop a consumer
         | 
| 334 382 | 
             
                    # @param consumer_tag [String] Tag of the consumer to cancel
         | 
| 335 383 | 
             
                    # @param no_wait [Boolean] Will wait for a confirmation from the broker that the consumer is cancelled
         | 
| 336 384 | 
             
                    # @return [nil]
         | 
| 337 385 | 
             
                    def basic_cancel(consumer_tag, no_wait: false)
         | 
| 338 | 
            -
                      consumer = @consumers | 
| 339 | 
            -
                      return  | 
| 386 | 
            +
                      consumer = @consumers[consumer_tag]
         | 
| 387 | 
            +
                      return unless consumer
         | 
| 340 388 |  | 
| 341 389 | 
             
                      write_bytes FrameBytes.basic_cancel(@id, consumer_tag)
         | 
| 342 390 | 
             
                      expect(:basic_cancel_ok) unless no_wait
         | 
| 343 | 
            -
                       | 
| 391 | 
            +
                      @consumers.delete(consumer_tag)
         | 
| 392 | 
            +
                      close_consumer(consumer)
         | 
| 344 393 | 
             
                      nil
         | 
| 345 394 | 
             
                    end
         | 
| 346 395 |  | 
| @@ -357,6 +406,7 @@ module AMQP | |
| 357 406 |  | 
| 358 407 | 
             
                    # Acknowledge a message
         | 
| 359 408 | 
             
                    # @param delivery_tag [Integer] The delivery tag of the message to acknowledge
         | 
| 409 | 
            +
                    # @param multiple [Boolean] Ack all messages up to this message
         | 
| 360 410 | 
             
                    # @return [nil]
         | 
| 361 411 | 
             
                    def basic_ack(delivery_tag, multiple: false)
         | 
| 362 412 | 
             
                      write_bytes FrameBytes.basic_ack(@id, delivery_tag, multiple)
         | 
| @@ -387,7 +437,7 @@ module AMQP | |
| 387 437 | 
             
                    #   if true to any consumer
         | 
| 388 438 | 
             
                    # @return [nil]
         | 
| 389 439 | 
             
                    def basic_recover(requeue: false)
         | 
| 390 | 
            -
                      write_bytes FrameBytes.basic_recover(@id, requeue: | 
| 440 | 
            +
                      write_bytes FrameBytes.basic_recover(@id, requeue:)
         | 
| 391 441 | 
             
                      expect :basic_recover_ok
         | 
| 392 442 | 
             
                      nil
         | 
| 393 443 | 
             
                    end
         | 
| @@ -413,20 +463,23 @@ module AMQP | |
| 413 463 | 
             
                    end
         | 
| 414 464 |  | 
| 415 465 | 
             
                    # Block until all publishes messages are confirmed
         | 
| 416 | 
            -
                    # @return  | 
| 466 | 
            +
                    # @return [Boolean] True if all messages were acked, false if any were nacked
         | 
| 417 467 | 
             
                    def wait_for_confirms
         | 
| 418 468 | 
             
                      @unconfirmed_lock.synchronize do
         | 
| 419 469 | 
             
                        until @unconfirmed.empty?
         | 
| 420 470 | 
             
                          @unconfirmed_empty.wait(@unconfirmed_lock)
         | 
| 421 471 | 
             
                          raise Error::Closed.new(@id, *@closed) if @closed
         | 
| 422 472 | 
             
                        end
         | 
| 473 | 
            +
                        result = !@nacked
         | 
| 474 | 
            +
                        @nacked = false # Reset for next round of publishes
         | 
| 475 | 
            +
                        result
         | 
| 423 476 | 
             
                      end
         | 
| 424 477 | 
             
                    end
         | 
| 425 478 |  | 
| 426 479 | 
             
                    # Called by Connection when received ack/nack from broker
         | 
| 427 480 | 
             
                    # @api private
         | 
| 428 481 | 
             
                    def confirm(args)
         | 
| 429 | 
            -
                       | 
| 482 | 
            +
                      ack_or_nack, delivery_tag, multiple = *args
         | 
| 430 483 | 
             
                      @unconfirmed_lock.synchronize do
         | 
| 431 484 | 
             
                        case multiple
         | 
| 432 485 | 
             
                        when true
         | 
| @@ -435,6 +488,7 @@ module AMQP | |
| 435 488 | 
             
                        when false
         | 
| 436 489 | 
             
                          @unconfirmed.delete(delivery_tag) || raise("Delivery tag not found")
         | 
| 437 490 | 
             
                        end
         | 
| 491 | 
            +
                        @nacked = true if ack_or_nack == :nack
         | 
| 438 492 | 
             
                        @unconfirmed_empty.broadcast if @unconfirmed.empty?
         | 
| 439 493 | 
             
                      end
         | 
| 440 494 | 
             
                    end
         | 
| @@ -508,14 +562,29 @@ module AMQP | |
| 508 562 | 
             
                      next_message_finished!
         | 
| 509 563 | 
             
                    end
         | 
| 510 564 |  | 
| 565 | 
            +
                    # Handle consumer cancellation from the broker
         | 
| 511 566 | 
             
                    # @api private
         | 
| 512 | 
            -
                    def  | 
| 513 | 
            -
                      @consumers. | 
| 567 | 
            +
                    def cancel_consumer(tag)
         | 
| 568 | 
            +
                      consumer = @consumers.delete(tag)
         | 
| 569 | 
            +
                      return unless consumer
         | 
| 570 | 
            +
             | 
| 571 | 
            +
                      close_consumer(consumer)
         | 
| 572 | 
            +
                      begin
         | 
| 573 | 
            +
                        consumer.on_cancel&.call(consumer.consumer_tag)
         | 
| 574 | 
            +
                      rescue StandardError => e
         | 
| 575 | 
            +
                        warn "AMQP-Client consumer on_cancel callback error: #{e.class}: #{e.message}"
         | 
| 576 | 
            +
                      end
         | 
| 514 577 | 
             
                      nil
         | 
| 515 578 | 
             
                    end
         | 
| 516 579 |  | 
| 517 580 | 
             
                    private
         | 
| 518 581 |  | 
| 582 | 
            +
                    def close_consumer(consumer)
         | 
| 583 | 
            +
                      consumer.msg_q.close
         | 
| 584 | 
            +
                      # The worker threads will exit when the queue is closed
         | 
| 585 | 
            +
                      nil
         | 
| 586 | 
            +
                    end
         | 
| 587 | 
            +
             | 
| 519 588 | 
             
                    def next_message_finished!
         | 
| 520 589 | 
             
                      next_msg = @next_msg
         | 
| 521 590 | 
             
                      if next_msg.is_a? ReturnMessage
         | 
| @@ -528,7 +597,7 @@ module AMQP | |
| 528 597 | 
             
                        @basic_gets.push next_msg
         | 
| 529 598 | 
             
                      else
         | 
| 530 599 | 
             
                        Thread.pass until (consumer = @consumers[next_msg.consumer_tag])
         | 
| 531 | 
            -
                        consumer.push next_msg
         | 
| 600 | 
            +
                        consumer.msg_q.push next_msg
         | 
| 532 601 | 
             
                      end
         | 
| 533 602 | 
             
                      nil
         | 
| 534 603 | 
             
                    ensure
         | 
| @@ -544,7 +613,7 @@ module AMQP | |
| 544 613 | 
             
                    def expect(expected_frame_type)
         | 
| 545 614 | 
             
                      frame_type, *args = @replies.pop
         | 
| 546 615 | 
             
                      raise Error::Closed.new(@id, *@closed) if frame_type.nil?
         | 
| 547 | 
            -
                      raise Error:: | 
| 616 | 
            +
                      raise Error::UnexpectedFrameType.new(expected_frame_type, frame_type) unless frame_type == expected_frame_type
         | 
| 548 617 |  | 
| 549 618 | 
             
                      args
         | 
| 550 619 | 
             
                    end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AMQP
         | 
| 4 | 
            +
              class Client
         | 
| 5 | 
            +
                # Configuration for AMQP::Client
         | 
| 6 | 
            +
                # @!attribute strict_coding
         | 
| 7 | 
            +
                #   @return [Boolean] Whether to raise on unknown codecs
         | 
| 8 | 
            +
                # @!attribute default_content_type
         | 
| 9 | 
            +
                #   @return [String, nil] Default content type for published messages
         | 
| 10 | 
            +
                # @!attribute default_content_encoding
         | 
| 11 | 
            +
                #   @return [String, nil] Default content encoding for published messages
         | 
| 12 | 
            +
                class Configuration
         | 
| 13 | 
            +
                  attr_accessor :strict_coding, :default_content_type, :default_content_encoding
         | 
| 14 | 
            +
                  attr_reader :codec_registry
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # Initialize a new configuration
         | 
| 17 | 
            +
                  # @param codec_registry [MessageCodecRegistry] The codec registry to use
         | 
| 18 | 
            +
                  def initialize(codec_registry)
         | 
| 19 | 
            +
                    @codec_registry = codec_registry
         | 
| 20 | 
            +
                    @strict_coding = false
         | 
| 21 | 
            +
                    @default_content_type = nil
         | 
| 22 | 
            +
                    @default_content_encoding = nil
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  # Enable all built-in codecs (parsers and coders) for automatic message encoding/decoding
         | 
| 26 | 
            +
                  # This is a convenience method that delegates to the codec registry
         | 
| 27 | 
            +
                  # @return [self]
         | 
| 28 | 
            +
                  def enable_builtin_codecs
         | 
| 29 | 
            +
                    codec_registry.enable_builtin_codecs
         | 
| 30 | 
            +
                    self
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  # Enable built-in parsers for common content types
         | 
| 34 | 
            +
                  # @return [self]
         | 
| 35 | 
            +
                  def enable_builtin_parsers
         | 
| 36 | 
            +
                    codec_registry.enable_builtin_parsers
         | 
| 37 | 
            +
                    self
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  # Enable built-in coders for common content encodings
         | 
| 41 | 
            +
                  # @return [self]
         | 
| 42 | 
            +
                  def enable_builtin_coders
         | 
| 43 | 
            +
                    codec_registry.enable_builtin_coders
         | 
| 44 | 
            +
                    self
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  # Register a custom parser for a content type
         | 
| 48 | 
            +
                  # @param content_type [String] The content_type to match
         | 
| 49 | 
            +
                  # @param parser [Object] The parser object
         | 
| 50 | 
            +
                  # @return [self]
         | 
| 51 | 
            +
                  def register_parser(content_type:, parser:)
         | 
| 52 | 
            +
                    codec_registry.register_parser(content_type:, parser:)
         | 
| 53 | 
            +
                    self
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  # Register a custom coder for a content encoding
         | 
| 57 | 
            +
                  # @param content_encoding [String] The content_encoding to match
         | 
| 58 | 
            +
                  # @param coder [Object] The coder object
         | 
| 59 | 
            +
                  # @return [self]
         | 
| 60 | 
            +
                  def register_coder(content_encoding:, coder:)
         | 
| 61 | 
            +
                    codec_registry.register_coder(content_encoding:, coder:)
         | 
| 62 | 
            +
                    self
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -3,9 +3,9 @@ | |
| 3 3 | 
             
            require "socket"
         | 
| 4 4 | 
             
            require "uri"
         | 
| 5 5 | 
             
            require "openssl"
         | 
| 6 | 
            -
            require_relative " | 
| 7 | 
            -
            require_relative " | 
| 8 | 
            -
            require_relative " | 
| 6 | 
            +
            require_relative "frame_bytes"
         | 
| 7 | 
            +
            require_relative "channel"
         | 
| 8 | 
            +
            require_relative "errors"
         | 
| 9 9 |  | 
| 10 10 | 
             
            module AMQP
         | 
| 11 11 | 
             
              class Client
         | 
| @@ -15,6 +15,8 @@ module AMQP | |
| 15 15 | 
             
                  # @param uri [String] URL on the format amqp://username:password@hostname/vhost, use amqps:// for encrypted connection
         | 
| 16 16 | 
             
                  # @param read_loop_thread [Boolean] If true run {#read_loop} in a background thread,
         | 
| 17 17 | 
             
                  #   otherwise the user have to run it explicitly, without {#read_loop} the connection won't function
         | 
| 18 | 
            +
                  # @param codec_registry [MessageCodecRegistry] Registry for message codecs
         | 
| 19 | 
            +
                  # @param strict_coding [Boolean] Whether to raise errors on unsupported codecs
         | 
| 18 20 | 
             
                  # @option options [Boolean] connection_name (PROGRAM_NAME) Set a name for the connection to be able to identify
         | 
| 19 21 | 
             
                  #   the client from the broker
         | 
| 20 22 | 
             
                  # @option options [Boolean] verify_peer (true) Verify broker's TLS certificate, set to false for self-signed certs
         | 
| @@ -26,7 +28,7 @@ module AMQP | |
| 26 28 | 
             
                  #   Maxium allowed is 65_536.  The smallest of the client's and the broker's value will be used.
         | 
| 27 29 | 
             
                  # @option options [String] keepalive (60:10:3) TCP keepalive setting, 60s idle, 10s interval between probes, 3 probes
         | 
| 28 30 | 
             
                  # @return [Connection]
         | 
| 29 | 
            -
                  def initialize(uri = "", read_loop_thread: true, **options)
         | 
| 31 | 
            +
                  def initialize(uri = "", read_loop_thread: true, codec_registry: nil, strict_coding: false, **options)
         | 
| 30 32 | 
             
                    uri = URI.parse(uri)
         | 
| 31 33 | 
             
                    tls = uri.scheme == "amqps"
         | 
| 32 34 | 
             
                    port = port_from_env || uri.port || (tls ? 5671 : 5672)
         | 
| @@ -43,6 +45,8 @@ module AMQP | |
| 43 45 | 
             
                    @channel_max = channel_max.zero? ? 65_536 : channel_max
         | 
| 44 46 | 
             
                    @frame_max = frame_max
         | 
| 45 47 | 
             
                    @heartbeat = heartbeat
         | 
| 48 | 
            +
                    @codec_registry = codec_registry
         | 
| 49 | 
            +
                    @strict_coding = strict_coding
         | 
| 46 50 | 
             
                    @channels = {}
         | 
| 47 51 | 
             
                    @channels_lock = Mutex.new
         | 
| 48 52 | 
             
                    @closed = nil
         | 
| @@ -71,14 +75,22 @@ module AMQP | |
| 71 75 | 
             
                  # Alias for {#initialize}
         | 
| 72 76 | 
             
                  # @see #initialize
         | 
| 73 77 | 
             
                  # @deprecated
         | 
| 74 | 
            -
                  def self.connect(uri, read_loop_thread: true, ** | 
| 75 | 
            -
                    new(uri, read_loop_thread | 
| 78 | 
            +
                  def self.connect(uri, read_loop_thread: true, **)
         | 
| 79 | 
            +
                    new(uri, read_loop_thread:, **)
         | 
| 76 80 | 
             
                  end
         | 
| 77 81 |  | 
| 78 82 | 
             
                  # The max frame size negotiated between the client and the broker
         | 
| 79 83 | 
             
                  # @return [Integer]
         | 
| 80 84 | 
             
                  attr_reader :frame_max
         | 
| 81 85 |  | 
| 86 | 
            +
                  # The codec registry for message encoding/decoding
         | 
| 87 | 
            +
                  # @return [MessageCodecRegistry, nil]
         | 
| 88 | 
            +
                  attr_reader :codec_registry
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  # Whether to use strict coding (raise errors on unsupported codecs)
         | 
| 91 | 
            +
                  # @return [Boolean]
         | 
| 92 | 
            +
                  attr_reader :strict_coding
         | 
| 93 | 
            +
             | 
| 82 94 | 
             
                  # Custom inspect
         | 
| 83 95 | 
             
                  # @return [String]
         | 
| 84 96 | 
             
                  # @api private
         | 
| @@ -91,6 +103,7 @@ module AMQP | |
| 91 103 | 
             
                  # @return [Channel]
         | 
| 92 104 | 
             
                  def channel(id = nil)
         | 
| 93 105 | 
             
                    raise ArgumentError, "Channel ID cannot be 0" if id&.zero?
         | 
| 106 | 
            +
             | 
| 94 107 | 
             
                    raise ArgumentError, "Channel ID higher than connection's channel max #{@channel_max}" if id && id > @channel_max
         | 
| 95 108 |  | 
| 96 109 | 
             
                    ch = @channels_lock.synchronize do
         | 
| @@ -142,7 +155,7 @@ module AMQP | |
| 142 155 | 
             
                  # @param secret [String] The new secret
         | 
| 143 156 | 
             
                  # @param reason [String] A reason to update it
         | 
| 144 157 | 
             
                  # @return [nil]
         | 
| 145 | 
            -
                  def update_secret(secret, reason)
         | 
| 158 | 
            +
                  def update_secret(secret, reason:)
         | 
| 146 159 | 
             
                    write_bytes FrameBytes.update_secret(secret, reason)
         | 
| 147 160 | 
             
                    expect(:update_secret_ok)
         | 
| 148 161 | 
             
                    nil
         | 
| @@ -202,14 +215,15 @@ module AMQP | |
| 202 215 | 
             
                    loop do
         | 
| 203 216 | 
             
                      socket.read(7, frame_start) || raise(IOError)
         | 
| 204 217 | 
             
                      type, channel_id, frame_size = frame_start.unpack("C S> L>")
         | 
| 205 | 
            -
                      frame_max >= frame_size || | 
| 218 | 
            +
                      frame_max >= frame_size ||
         | 
| 219 | 
            +
                        raise(Error, "Frame size #{frame_size} larger than negotiated max frame size #{frame_max}")
         | 
| 206 220 |  | 
| 207 221 | 
             
                      # read the frame content
         | 
| 208 222 | 
             
                      socket.read(frame_size, frame_buffer) || raise(IOError)
         | 
| 209 223 |  | 
| 210 224 | 
             
                      # make sure that the frame end is correct
         | 
| 211 225 | 
             
                      frame_end = socket.readchar.ord
         | 
| 212 | 
            -
                      raise Error:: | 
| 226 | 
            +
                      raise Error::UnexpectedFrameTypeEnd, frame_end if frame_end != 206
         | 
| 213 227 |  | 
| 214 228 | 
             
                      # parse the frame, will return false if a close frame was received
         | 
| 215 229 | 
             
                      parse_frame(type, channel_id, frame_buffer) || return
         | 
| @@ -285,9 +299,11 @@ module AMQP | |
| 285 299 | 
             
                          reply_code, reply_text_len = buf.unpack("@4 S> C")
         | 
| 286 300 | 
             
                          reply_text = buf.byteslice(7, reply_text_len).force_encoding("utf-8")
         | 
| 287 301 | 
             
                          classid, methodid = buf.byteslice(7 + reply_text_len, 4).unpack("S> S>")
         | 
| 288 | 
            -
                           | 
| 289 | 
            -
             | 
| 290 | 
            -
             | 
| 302 | 
            +
                          @channels_lock.synchronize do
         | 
| 303 | 
            +
                            channel = @channels.delete(channel_id)
         | 
| 304 | 
            +
                            channel.closed!(:channel, reply_code, reply_text, classid, methodid)
         | 
| 305 | 
            +
                            write_bytes FrameBytes.channel_close_ok(channel_id)
         | 
| 306 | 
            +
                          end
         | 
| 291 307 | 
             
                        when 41 # channel#close-ok
         | 
| 292 308 | 
             
                          channel = @channels_lock.synchronize { @channels.delete(channel_id) }
         | 
| 293 309 | 
             
                          channel.reply [:channel_close_ok]
         | 
| @@ -336,7 +352,7 @@ module AMQP | |
| 336 352 | 
             
                          tag_len = buf.getbyte(4)
         | 
| 337 353 | 
             
                          tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
         | 
| 338 354 | 
             
                          no_wait = buf.getbyte(5 + tag_len) == 1
         | 
| 339 | 
            -
                          channel. | 
| 355 | 
            +
                          channel.cancel_consumer(tag)
         | 
| 340 356 | 
             
                          write_bytes FrameBytes.basic_cancel_ok(@id, tag) unless no_wait
         | 
| 341 357 | 
             
                        when 31 # cancel-ok
         | 
| 342 358 | 
             
                          tag_len = buf.getbyte(4)
         | 
| @@ -470,7 +486,7 @@ module AMQP | |
| 470 486 |  | 
| 471 487 | 
             
                      raise Error, "Connection closed while waiting for #{expected_frame_type}"
         | 
| 472 488 | 
             
                    end
         | 
| 473 | 
            -
                    frame_type == expected_frame_type || raise(Error:: | 
| 489 | 
            +
                    frame_type == expected_frame_type || raise(Error::UnexpectedFrameType.new(expected_frame_type, frame_type))
         | 
| 474 490 | 
             
                    args
         | 
| 475 491 | 
             
                  end
         | 
| 476 492 |  | 
| @@ -479,7 +495,8 @@ module AMQP | |
| 479 495 | 
             
                  # @return [OpenSSL::SSL::SSLSocket]
         | 
| 480 496 | 
             
                  def open_socket(host, port, tls, options)
         | 
| 481 497 | 
             
                    connect_timeout = options.fetch(:connect_timeout, 30).to_f
         | 
| 482 | 
            -
                    socket = Socket.tcp | 
| 498 | 
            +
                    socket = Socket.tcp(host, port, connect_timeout:)
         | 
| 499 | 
            +
                    socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
         | 
| 483 500 | 
             
                    keepalive = options.fetch(:keepalive, "").split(":", 3).map!(&:to_i)
         | 
| 484 501 | 
             
                    enable_tcp_keepalive(socket, *keepalive)
         | 
| 485 502 | 
             
                    if tls
         | 
| @@ -515,7 +532,7 @@ module AMQP | |
| 515 532 |  | 
| 516 533 | 
             
                      type, channel_id, frame_size = buf.unpack("C S> L>")
         | 
| 517 534 | 
             
                      frame_end = buf.getbyte(frame_size + 7)
         | 
| 518 | 
            -
                      raise Error:: | 
| 535 | 
            +
                      raise Error::UnexpectedFrameTypeEnd, frame_end if frame_end != 206
         | 
| 519 536 |  | 
| 520 537 | 
             
                      case type
         | 
| 521 538 | 
             
                      when 1 # method frame
         | 
| @@ -558,7 +575,7 @@ module AMQP | |
| 558 575 | 
             
                    rescue *READ_EXCEPTIONS
         | 
| 559 576 | 
             
                      nil
         | 
| 560 577 | 
             
                    end
         | 
| 561 | 
            -
                    raise e
         | 
| 578 | 
            +
                    raise Error, "Could not establish AMQP connection: invalid handshake (#{e.message})"
         | 
| 562 579 | 
             
                  end
         | 
| 563 580 |  | 
| 564 581 | 
             
                  # Enable TCP keepalive, which is preferred to heartbeats
         | 
| @@ -583,7 +600,7 @@ module AMQP | |
| 583 600 | 
             
                  # @return [Integer] A port number
         | 
| 584 601 | 
             
                  # @return [nil] When the environment variable AMQP_PORT isn't set
         | 
| 585 602 | 
             
                  def port_from_env
         | 
| 586 | 
            -
                    return unless (port = ENV | 
| 603 | 
            +
                    return unless (port = ENV.fetch("AMQP_PORT", nil))
         | 
| 587 604 |  | 
| 588 605 | 
             
                    port.to_i
         | 
| 589 606 | 
             
                  end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AMQP
         | 
| 4 | 
            +
              class Client
         | 
| 5 | 
            +
                # Consumer abstraction
         | 
| 6 | 
            +
                class Consumer
         | 
| 7 | 
            +
                  attr_reader :queue, :id, :channel_id, :prefetch, :block, :basic_consume_args
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # @api private
         | 
| 10 | 
            +
                  def initialize(client:, channel_id:, id:, block:, **settings)
         | 
| 11 | 
            +
                    @client = client
         | 
| 12 | 
            +
                    @channel_id = channel_id
         | 
| 13 | 
            +
                    @id = id
         | 
| 14 | 
            +
                    @queue = settings.fetch(:queue)
         | 
| 15 | 
            +
                    @basic_consume_args = settings.fetch(:basic_consume_args)
         | 
| 16 | 
            +
                    @prefetch = settings.fetch(:prefetch)
         | 
| 17 | 
            +
                    @consume_ok = settings.fetch(:consume_ok)
         | 
| 18 | 
            +
                    @block = block
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  # Cancel the consumer
         | 
| 22 | 
            +
                  # @return [self]
         | 
| 23 | 
            +
                  def cancel
         | 
| 24 | 
            +
                    @client.cancel_consumer(self)
         | 
| 25 | 
            +
                    self
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # True if the consumer is cancelled/closed
         | 
| 29 | 
            +
                  # @return [Boolean]
         | 
| 30 | 
            +
                  def closed?
         | 
| 31 | 
            +
                    @consume_ok.msg_q.closed?
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  # Return the consumer tag
         | 
| 35 | 
            +
                  # @return [String]
         | 
| 36 | 
            +
                  def tag
         | 
| 37 | 
            +
                    @consume_ok.consumer_tag
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  # Update the consumer with new metadata after reconnection
         | 
| 41 | 
            +
                  # @api private
         | 
| 42 | 
            +
                  def update_consume_ok(consume_ok)
         | 
| 43 | 
            +
                    @consume_ok = consume_ok
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         |