garaio_bunny 2.19.1

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +231 -0
  3. data/lib/amq/protocol/extensions.rb +16 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +55 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
  7. data/lib/bunny/channel.rb +2055 -0
  8. data/lib/bunny/channel_id_allocator.rb +82 -0
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
  10. data/lib/bunny/concurrent/condition.rb +66 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +62 -0
  12. data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
  13. data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
  14. data/lib/bunny/consumer.rb +128 -0
  15. data/lib/bunny/consumer_tag_generator.rb +23 -0
  16. data/lib/bunny/consumer_work_pool.rb +122 -0
  17. data/lib/bunny/cruby/socket.rb +110 -0
  18. data/lib/bunny/cruby/ssl_socket.rb +118 -0
  19. data/lib/bunny/delivery_info.rb +93 -0
  20. data/lib/bunny/exceptions.rb +269 -0
  21. data/lib/bunny/exchange.rb +275 -0
  22. data/lib/bunny/framing.rb +56 -0
  23. data/lib/bunny/get_response.rb +83 -0
  24. data/lib/bunny/heartbeat_sender.rb +71 -0
  25. data/lib/bunny/jruby/socket.rb +57 -0
  26. data/lib/bunny/jruby/ssl_socket.rb +58 -0
  27. data/lib/bunny/message_properties.rb +119 -0
  28. data/lib/bunny/queue.rb +393 -0
  29. data/lib/bunny/reader_loop.rb +158 -0
  30. data/lib/bunny/return_info.rb +74 -0
  31. data/lib/bunny/session.rb +1483 -0
  32. data/lib/bunny/socket.rb +14 -0
  33. data/lib/bunny/ssl_socket.rb +14 -0
  34. data/lib/bunny/test_kit.rb +41 -0
  35. data/lib/bunny/timeout.rb +7 -0
  36. data/lib/bunny/transport.rb +526 -0
  37. data/lib/bunny/version.rb +6 -0
  38. data/lib/bunny/versioned_delivery_tag.rb +28 -0
  39. data/lib/bunny.rb +92 -0
  40. metadata +127 -0
@@ -0,0 +1,2055 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "thread"
3
+ require "monitor"
4
+ require "set"
5
+
6
+ require "bunny/concurrent/atomic_fixnum"
7
+ require "bunny/consumer_work_pool"
8
+
9
+ require "bunny/exchange"
10
+ require "bunny/queue"
11
+
12
+ require "bunny/delivery_info"
13
+ require "bunny/return_info"
14
+ require "bunny/message_properties"
15
+
16
+ if defined?(JRUBY_VERSION)
17
+ require "bunny/concurrent/linked_continuation_queue"
18
+ else
19
+ require "bunny/concurrent/continuation_queue"
20
+ end
21
+
22
+ module Bunny
23
+ # ## Channels in RabbitMQ
24
+ #
25
+ # To quote {http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification}:
26
+ #
27
+ # AMQP 0.9.1 is a multi-channelled protocol. Channels provide a way to multiplex
28
+ # a heavyweight TCP/IP connection into several light weight connections.
29
+ # This makes the protocol more “firewall friendly” since port usage is predictable.
30
+ # It also means that traffic shaping and other network QoS features can be easily employed.
31
+ # Channels are independent of each other and can perform different functions simultaneously
32
+ # with other channels, the available bandwidth being shared between the concurrent activities.
33
+ #
34
+ #
35
+ # ## Opening Channels
36
+ #
37
+ # Channels can be opened either via `Bunny::Session#create_channel` (sufficient in the majority
38
+ # of cases) or by instantiating `Bunny::Channel` directly:
39
+ #
40
+ # conn = Bunny.new
41
+ # conn.start
42
+ #
43
+ # ch = conn.create_channel
44
+ #
45
+ # This will automatically allocate a channel id.
46
+ #
47
+ # ## Closing Channels
48
+ #
49
+ # Channels are closed via {Bunny::Channel#close}. Channels that get a channel-level exception are
50
+ # closed, too. Closed channels can no longer be used. Attempts to use them will raise
51
+ # {Bunny::ChannelAlreadyClosed}.
52
+ #
53
+ # ch = conn.create_channel
54
+ # ch.close
55
+ #
56
+ # ## Higher-level API
57
+ #
58
+ # Bunny offers two sets of methods on {Bunny::Channel}: known as higher-level and lower-level
59
+ # APIs, respectively. Higher-level API mimics {http://rubyamqp.info amqp gem} API where
60
+ # exchanges and queues are objects (instance of {Bunny::Exchange} and {Bunny::Queue}, respectively).
61
+ # Lower-level API is built around AMQP 0.9.1 methods (commands), where queues and exchanges are
62
+ # passed as strings (à la RabbitMQ Java client, {http://clojurerabbitmq.info Langohr} and Pika).
63
+ #
64
+ # ### Queue Operations In Higher-level API
65
+ #
66
+ # * {Bunny::Channel#queue} is used to declare queues. The rest of the API is in {Bunny::Queue}.
67
+ #
68
+ #
69
+ # ### Exchange Operations In Higher-level API
70
+ #
71
+ # * {Bunny::Channel#topic} declares a topic exchange. The rest of the API is in {Bunny::Exchange}.
72
+ # * {Bunny::Channel#direct} declares a direct exchange.
73
+ # * {Bunny::Channel#fanout} declares a fanout exchange.
74
+ # * {Bunny::Channel#headers} declares a headers exchange.
75
+ # * {Bunny::Channel#default_exchange}
76
+ # * {Bunny::Channel#exchange} is used to declare exchanges with type specified as a symbol or string.
77
+ #
78
+ #
79
+ # ## Channel Qos (Prefetch Level)
80
+ #
81
+ # It is possible to control how many messages at most a consumer will be given (before it acknowledges
82
+ # or rejects previously consumed ones). This setting is per channel and controlled via {Bunny::Channel#prefetch}.
83
+ #
84
+ #
85
+ # ## Channel IDs
86
+ #
87
+ # Channels are identified by their ids which are integers. Bunny takes care of allocating and
88
+ # releasing them as channels are opened and closed. It is almost never necessary to specify
89
+ # channel ids explicitly.
90
+ #
91
+ # There is a limit on the maximum number of channels per connection, usually 65536. Note
92
+ # that allocating channels is very cheap on both client and server so having tens, hundreds
93
+ # or even thousands of channels is not a problem.
94
+ #
95
+ # ## Channels and Error Handling
96
+ #
97
+ # Channel-level exceptions are more common than connection-level ones and often indicate
98
+ # issues applications can recover from (such as consuming from or trying to delete
99
+ # a queue that does not exist).
100
+ #
101
+ # With Bunny, channel-level exceptions are raised as Ruby exceptions, for example,
102
+ # {Bunny::NotFound}, that provide access to the underlying `channel.close` method
103
+ # information.
104
+ #
105
+ # @example Handling 404 NOT_FOUND
106
+ # begin
107
+ # ch.queue_delete("queue_that_should_not_exist#{rand}")
108
+ # rescue Bunny::NotFound => e
109
+ # puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
110
+ # end
111
+ #
112
+ # @example Handling 406 PRECONDITION_FAILED
113
+ # begin
114
+ # ch2 = conn.create_channel
115
+ # q = "bunny.examples.recovery.q#{rand}"
116
+ #
117
+ # ch2.queue_declare(q, :durable => false)
118
+ # ch2.queue_declare(q, :durable => true)
119
+ # rescue Bunny::PreconditionFailed => e
120
+ # puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
121
+ # ensure
122
+ # conn.create_channel.queue_delete(q)
123
+ # end
124
+ #
125
+ # @see http://www.rabbitmq.com/tutorials/amqp-concepts.html AMQP 0.9.1 Model Concepts Guide
126
+ # @see http://rubybunny.info/articles/getting_started.html Getting Started with RabbitMQ Using Bunny
127
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers
128
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing
129
+ # @see http://rubybunny.info/articles/error_handling.html Error Handling and Recovery Guide
130
+ class Channel
131
+
132
+ #
133
+ # API
134
+ #
135
+
136
+ # @return [Integer] Channel id
137
+ attr_accessor :id
138
+ # @return [Bunny::Session] AMQP connection this channel was opened on
139
+ attr_reader :connection
140
+ # @return [Symbol] Channel status (:opening, :open, :closed)
141
+ attr_reader :status
142
+ # @return [Bunny::ConsumerWorkPool] Thread pool delivered messages are dispatched to.
143
+ attr_reader :work_pool
144
+ # @return [Integer] Next publisher confirmations sequence index
145
+ attr_reader :next_publish_seq_no
146
+ # @return [Hash<String, Bunny::Queue>] Queue instances declared on this channel
147
+ attr_reader :queues
148
+ # @return [Hash<String, Bunny::Exchange>] Exchange instances declared on this channel
149
+ attr_reader :exchanges
150
+ # @return [Set<Integer>] Set of published message indexes that are currently unconfirmed
151
+ attr_reader :unconfirmed_set
152
+ # @return [Set<Integer>] Set of nacked message indexes that have been nacked
153
+ attr_reader :nacked_set
154
+ # @return [Hash<String, Bunny::Consumer>] Consumer instances declared on this channel
155
+ attr_reader :consumers
156
+
157
+ # @return [Integer] active basic.qos prefetch value
158
+ attr_reader :prefetch_count
159
+ # @return [Integer] active basic.qos prefetch global mode
160
+ attr_reader :prefetch_global
161
+
162
+ DEFAULT_CONTENT_TYPE = "application/octet-stream".freeze
163
+ SHORTSTR_LIMIT = 255
164
+
165
+ # @param [Bunny::Session] connection AMQP 0.9.1 connection
166
+ # @param [Integer] id Channel id, pass nil to make Bunny automatically allocate it
167
+ # @param [Bunny::ConsumerWorkPool] work_pool Thread pool for delivery processing, by default of size 1
168
+ def initialize(connection = nil, id = nil, work_pool = ConsumerWorkPool.new(1))
169
+ @connection = connection
170
+ @logger = connection.logger
171
+ @id = id || @connection.next_channel_id
172
+
173
+ # channel allocator is exhausted
174
+ if @id < 0
175
+ msg = "Cannot open a channel: max number of channels on connection reached. Connection channel_max value: #{@connection.channel_max}"
176
+ @logger.error(msg)
177
+
178
+ raise msg
179
+ else
180
+ @logger.debug { "Allocated channel id: #{@id}" }
181
+ end
182
+
183
+ @status = :opening
184
+
185
+ @connection.register_channel(self)
186
+
187
+ @queues = Hash.new
188
+ @exchanges = Hash.new
189
+ @consumers = Hash.new
190
+ @work_pool = work_pool
191
+
192
+ # synchronizes frameset delivery. MK.
193
+ @publishing_mutex = @connection.mutex_impl.new
194
+ @consumer_mutex = @connection.mutex_impl.new
195
+
196
+ @queue_mutex = @connection.mutex_impl.new
197
+ @exchange_mutex = @connection.mutex_impl.new
198
+
199
+ @unconfirmed_set_mutex = @connection.mutex_impl.new
200
+
201
+ self.reset_continuations
202
+
203
+ # threads awaiting on continuations. Used to unblock
204
+ # them when network connection goes down so that busy loops
205
+ # that perform synchronous operations can work. MK.
206
+ @threads_waiting_on_continuations = Set.new
207
+ @threads_waiting_on_confirms_continuations = Set.new
208
+ @threads_waiting_on_basic_get_continuations = Set.new
209
+
210
+ @next_publish_seq_no = 0
211
+ @delivery_tag_offset = 0
212
+
213
+ @recoveries_counter = Bunny::Concurrent::AtomicFixnum.new(0)
214
+ @uncaught_exception_handler = Proc.new do |e, consumer|
215
+ @logger.error "Uncaught exception from consumer #{consumer.to_s}: #{e.inspect} @ #{e.backtrace[0]}"
216
+ end
217
+ end
218
+
219
+ attr_reader :recoveries_counter
220
+
221
+ # @private
222
+ def wait_on_continuations_timeout
223
+ @connection.transport_write_timeout
224
+ end
225
+
226
+ # Opens the channel and resets its internal state
227
+ # @return [Bunny::Channel] Self
228
+ # @api public
229
+ def open
230
+ @threads_waiting_on_continuations = Set.new
231
+ @threads_waiting_on_confirms_continuations = Set.new
232
+ @threads_waiting_on_basic_get_continuations = Set.new
233
+
234
+ @connection.open_channel(self)
235
+ # clear last channel error
236
+ @last_channel_error = nil
237
+
238
+ @status = :open
239
+
240
+ self
241
+ end
242
+
243
+ # Closes the channel. Closed channels can no longer be used (this includes associated
244
+ # {Bunny::Queue}, {Bunny::Exchange} and {Bunny::Consumer} instances.
245
+ # @api public
246
+ def close
247
+ # see bunny#528
248
+ raise_if_no_longer_open!
249
+
250
+ @connection.close_channel(self)
251
+ @status = :closed
252
+ @work_pool.shutdown
253
+ maybe_kill_consumer_work_pool!
254
+ end
255
+
256
+ # @return [Boolean] true if this channel is open, false otherwise
257
+ # @api public
258
+ def open?
259
+ @status == :open
260
+ end
261
+
262
+ # @return [Boolean] true if this channel is closed (manually or because of an exception), false otherwise
263
+ # @api public
264
+ def closed?
265
+ @status == :closed
266
+ end
267
+
268
+ #
269
+ # @group Backwards compatibility with 0.8.0
270
+ #
271
+
272
+ # @return [Integer] Channel id
273
+ def number
274
+ self.id
275
+ end
276
+
277
+ # @return [Boolean] true if this channel is open
278
+ def active
279
+ open?
280
+ end
281
+
282
+ # @return [Bunny::Session] Connection this channel was opened on
283
+ def client
284
+ @connection
285
+ end
286
+
287
+ # @private
288
+ def frame_size
289
+ @connection.frame_max
290
+ end
291
+
292
+ # @endgroup
293
+
294
+
295
+ #
296
+ # Higher-level API, similar to amqp gem
297
+ #
298
+
299
+ # @group Higher-level API for exchange operations
300
+
301
+ # Declares a fanout exchange or looks it up in the cache of previously
302
+ # declared exchanges.
303
+ #
304
+ # @param [String] name Exchange name
305
+ # @param [Hash] opts Exchange parameters
306
+ #
307
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
308
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
309
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions)
310
+ #
311
+ # @return [Bunny::Exchange] Exchange instance
312
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
313
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
314
+ # @api public
315
+ def fanout(name, opts = {})
316
+ find_exchange(name) || Exchange.new(self, :fanout, name, opts)
317
+ end
318
+
319
+ # Declares a direct exchange or looks it up in the cache of previously
320
+ # declared exchanges.
321
+ #
322
+ # @param [String] name Exchange name
323
+ # @param [Hash] opts Exchange parameters
324
+ #
325
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
326
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
327
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions)
328
+ #
329
+ # @return [Bunny::Exchange] Exchange instance
330
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
331
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
332
+ # @api public
333
+ def direct(name, opts = {})
334
+ find_exchange(name) || Exchange.new(self, :direct, name, opts)
335
+ end
336
+
337
+ # Declares a topic exchange or looks it up in the cache of previously
338
+ # declared exchanges.
339
+ #
340
+ # @param [String] name Exchange name
341
+ # @param [Hash] opts Exchange parameters
342
+ #
343
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
344
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
345
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions)
346
+ #
347
+ # @return [Bunny::Exchange] Exchange instance
348
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
349
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
350
+ # @api public
351
+ def topic(name, opts = {})
352
+ find_exchange(name) || Exchange.new(self, :topic, name, opts)
353
+ end
354
+
355
+ # Declares a headers exchange or looks it up in the cache of previously
356
+ # declared exchanges.
357
+ #
358
+ # @param [String] name Exchange name
359
+ # @param [Hash] opts Exchange parameters
360
+ #
361
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
362
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
363
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments
364
+ #
365
+ # @return [Bunny::Exchange] Exchange instance
366
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
367
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
368
+ # @api public
369
+ def headers(name, opts = {})
370
+ find_exchange(name) || Exchange.new(self, :headers, name, opts)
371
+ end
372
+
373
+ # Provides access to the default exchange
374
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
375
+ # @api public
376
+ def default_exchange
377
+ Exchange.default(self)
378
+ end
379
+
380
+ # Declares a headers exchange or looks it up in the cache of previously
381
+ # declared exchanges.
382
+ #
383
+ # @param [String] name Exchange name
384
+ # @param [Hash] opts Exchange parameters
385
+ #
386
+ # @option opts [String,Symbol] :type (:direct) Exchange type, e.g. :fanout or "x-consistent-hash"
387
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
388
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
389
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments
390
+ #
391
+ # @return [Bunny::Exchange] Exchange instance
392
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
393
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
394
+ def exchange(name, opts = {})
395
+ Exchange.new(self, opts.fetch(:type, :direct), name, opts)
396
+ end
397
+
398
+ # @endgroup
399
+
400
+
401
+ # @group Higher-level API for queue operations
402
+
403
+ # Declares a queue or looks it up in the per-channel cache.
404
+ #
405
+ # @param [String] name Queue name. Pass an empty string to declare a server-named queue (make RabbitMQ generate a unique name).
406
+ # @param [Hash] opts Queue properties and other options
407
+ #
408
+ # @option opts [Boolean] :durable (false) Should this queue be durable?
409
+ # @option opts [Boolean] :auto-delete (false) Should this queue be automatically deleted when the last consumer disconnects?
410
+ # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
411
+ # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
412
+ #
413
+ # @return [Bunny::Queue] Queue that was declared or looked up in the cache
414
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
415
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
416
+ # @api public
417
+ def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {})
418
+ throw ArgumentError.new("queue name must not be nil") if name.nil?
419
+
420
+ q = find_queue(name) || Bunny::Queue.new(self, name, opts)
421
+
422
+ register_queue(q)
423
+ end
424
+
425
+ # Declares a new server-named queue that is automatically deleted when the
426
+ # connection is closed.
427
+ #
428
+ # @return [Bunny::Queue] Queue that was declared
429
+ # @see #queue
430
+ # @api public
431
+ def temporary_queue(opts = {})
432
+ queue("", opts.merge(:exclusive => true))
433
+ end
434
+
435
+ # @endgroup
436
+
437
+
438
+ # @group QoS and Flow Control
439
+
440
+ # Flow control. When set to false, RabbitMQ will stop delivering messages on this
441
+ # channel.
442
+ #
443
+ # @param [Boolean] active Should messages to consumers on this channel be delivered?
444
+ # @api public
445
+ def flow(active)
446
+ channel_flow(active)
447
+ end
448
+
449
+ # Tells RabbitMQ to redeliver unacknowledged messages
450
+ # @api public
451
+ def recover(ignored = true)
452
+ # RabbitMQ only supports basic.recover with requeue = true
453
+ basic_recover(true)
454
+ end
455
+
456
+ # @endgroup
457
+
458
+
459
+
460
+ # @group Message acknowledgements
461
+
462
+ # Rejects a message. A rejected message can be requeued or
463
+ # dropped by RabbitMQ.
464
+ #
465
+ # @param [Integer] delivery_tag Delivery tag to reject
466
+ # @param [Boolean] requeue Should this message be requeued instead of dropping it?
467
+ # @see Bunny::Channel#ack
468
+ # @see Bunny::Channel#nack
469
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
470
+ # @api public
471
+ def reject(delivery_tag, requeue = false)
472
+ basic_reject(delivery_tag.to_i, requeue)
473
+ end
474
+
475
+ # Acknowledges a message. Acknowledged messages are completely removed from the queue.
476
+ #
477
+ # @param [Integer] delivery_tag Delivery tag to acknowledge
478
+ # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be acknowledged as well?
479
+ # @see Bunny::Channel#nack
480
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
481
+ # @api public
482
+ def ack(delivery_tag, multiple = false)
483
+ basic_ack(delivery_tag.to_i, multiple)
484
+ end
485
+ alias acknowledge ack
486
+
487
+ # Rejects a message. A rejected message can be requeued or
488
+ # dropped by RabbitMQ. This method is similar to {Bunny::Channel#reject} but
489
+ # supports rejecting multiple messages at once, and is usually preferred.
490
+ #
491
+ # @param [Integer] delivery_tag Delivery tag to reject
492
+ # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be rejected as well?
493
+ # @param [Boolean] requeue (false) Should this message be requeued instead of dropping it?
494
+ # @see Bunny::Channel#ack
495
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
496
+ # @api public
497
+ def nack(delivery_tag, multiple = false, requeue = false)
498
+ basic_nack(delivery_tag.to_i, multiple, requeue)
499
+ end
500
+
501
+ # @endgroup
502
+
503
+ #
504
+ # Lower-level API, exposes protocol operations as they are defined in the protocol,
505
+ # without any OO sugar on top, by design.
506
+ #
507
+
508
+ # @group Consumer and Message operations (basic.*)
509
+
510
+ # Publishes a message using basic.publish AMQP 0.9.1 method.
511
+ #
512
+ # @param [String] payload Message payload. It will never be modified by Bunny or RabbitMQ in any way.
513
+ # @param [String] exchange Exchange to publish to
514
+ # @param [String] routing_key Routing key
515
+ # @param [Hash] opts Publishing options
516
+ #
517
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?
518
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
519
+ # @option opts [Integer] :timestamp A timestamp associated with this message
520
+ # @option opts [Integer] :expiration Expiration time after which the message will be deleted
521
+ # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
522
+ # @option opts [String] :reply_to Queue name other apps should send the response to
523
+ # @option opts [String] :content_type Message content type (e.g. application/json)
524
+ # @option opts [String] :content_encoding Message content encoding (e.g. gzip)
525
+ # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
526
+ # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
527
+ # @option opts [String] :message_id Any message identifier
528
+ # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
529
+ # @option opts [String] :app_id Optional application ID
530
+ #
531
+ # @return [Bunny::Channel] Self
532
+ # @api public
533
+ def basic_publish(payload, exchange, routing_key, opts = {})
534
+ raise_if_no_longer_open!
535
+ raise ArgumentError, "routing key cannot be longer than #{SHORTSTR_LIMIT} characters" if routing_key && routing_key.size > SHORTSTR_LIMIT
536
+
537
+ exchange_name = if exchange.respond_to?(:name)
538
+ exchange.name
539
+ else
540
+ exchange
541
+ end
542
+
543
+ mode = if opts.fetch(:persistent, true)
544
+ 2
545
+ else
546
+ 1
547
+ end
548
+
549
+ opts[:delivery_mode] ||= mode
550
+ opts[:content_type] ||= DEFAULT_CONTENT_TYPE
551
+ opts[:priority] ||= 0
552
+
553
+ if @next_publish_seq_no > 0
554
+ @unconfirmed_set_mutex.synchronize do
555
+ @unconfirmed_set.add(@next_publish_seq_no)
556
+ @next_publish_seq_no += 1
557
+ end
558
+ end
559
+
560
+ frames = AMQ::Protocol::Basic::Publish.encode(@id,
561
+ payload,
562
+ opts,
563
+ exchange_name,
564
+ routing_key,
565
+ opts[:mandatory],
566
+ false,
567
+ @connection.frame_max)
568
+ @connection.send_frameset(frames, self)
569
+
570
+ self
571
+ end
572
+
573
+ # Synchronously fetches a message from the queue, if there are any. This method is
574
+ # for cases when the convenience of synchronous operations is more important than
575
+ # throughput.
576
+ #
577
+ # @param [String] queue Queue name
578
+ # @param [Hash] opts Options
579
+ #
580
+ # @option opts [Boolean] :ack (true) [DEPRECATED] Use :manual_ack instead
581
+ # @option opts [Boolean] :manual_ack (true) Will this message be acknowledged manually?
582
+ #
583
+ # @return [Array] A triple of delivery info, message properties and message content
584
+ #
585
+ # @example Using Bunny::Channel#basic_get with manual acknowledgements
586
+ # conn = Bunny.new
587
+ # conn.start
588
+ # ch = conn.create_channel
589
+ # # here we assume the queue already exists and has messages
590
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue1", :manual_ack => true)
591
+ # ch.acknowledge(delivery_info.delivery_tag)
592
+ # @see Bunny::Queue#pop
593
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
594
+ # @api public
595
+ def basic_get(queue, opts = {:manual_ack => true})
596
+ raise_if_no_longer_open!
597
+
598
+ unless opts[:ack].nil?
599
+ warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
600
+ opts[:manual_ack] = opts[:ack]
601
+ end
602
+
603
+ @connection.send_frame(AMQ::Protocol::Basic::Get.encode(@id, queue, !(opts[:manual_ack])))
604
+ # this is a workaround for the edge case when basic_get is called in a tight loop
605
+ # and network goes down we need to perform recovery. The problem is, basic_get will
606
+ # keep blocking the thread that calls it without clear way to constantly unblock it
607
+ # from the network activity loop (where recovery happens) with the current continuations
608
+ # implementation (and even more correct and convenient ones, such as wait/notify, should
609
+ # we implement them). So we return a triple of nils immediately which apps should be
610
+ # able to handle anyway as "got no message, no need to act". MK.
611
+ last_basic_get_response = if @connection.open?
612
+ begin
613
+ wait_on_basic_get_continuations
614
+ rescue Timeout::Error => e
615
+ raise_if_continuation_resulted_in_a_channel_error!
616
+ raise e
617
+ end
618
+ else
619
+ [nil, nil, nil]
620
+ end
621
+
622
+ raise_if_continuation_resulted_in_a_channel_error!
623
+ last_basic_get_response
624
+ end
625
+
626
+ # prefetch_count is of type short in the protocol. MK.
627
+ MAX_PREFETCH_COUNT = (2 ** 16) - 1
628
+
629
+ # Controls message delivery rate using basic.qos AMQP 0.9.1 method.
630
+ #
631
+ # @param [Integer] prefetch_count How many messages can consumers on this channel be given at a time
632
+ # (before they have to acknowledge or reject one of the earlier received messages)
633
+ # @param [Boolean] global
634
+ # Whether to use global mode for prefetch:
635
+ # - +false+: per-consumer
636
+ # - +true+: per-channel
637
+ # Note that the default value (+false+) hasn't actually changed, but
638
+ # previous documentation described that as meaning per-channel and
639
+ # unsupported in RabbitMQ, whereas it now actually appears to mean
640
+ # per-consumer and supported
641
+ # (https://www.rabbitmq.com/consumer-prefetch.html).
642
+ # @return [AMQ::Protocol::Basic::QosOk] RabbitMQ response
643
+ # @see Bunny::Channel#prefetch
644
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
645
+ # @api public
646
+ def basic_qos(count, global = false)
647
+ raise ArgumentError.new("prefetch count must be a positive integer, given: #{count}") if count < 0
648
+ raise ArgumentError.new("prefetch count must be no greater than #{MAX_PREFETCH_COUNT}, given: #{count}") if count > MAX_PREFETCH_COUNT
649
+ raise_if_no_longer_open!
650
+
651
+ @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
652
+
653
+ with_continuation_timeout do
654
+ @last_basic_qos_ok = wait_on_continuations
655
+ end
656
+ raise_if_continuation_resulted_in_a_channel_error!
657
+
658
+ @prefetch_count = count
659
+ @prefetch_global = global
660
+
661
+ @last_basic_qos_ok
662
+ end
663
+ alias prefetch basic_qos
664
+
665
+ # Redeliver unacknowledged messages
666
+ #
667
+ # @param [Boolean] requeue Should messages be requeued?
668
+ # @return [AMQ::Protocol::Basic::RecoverOk] RabbitMQ response
669
+ # @api public
670
+ def basic_recover(requeue)
671
+ raise_if_no_longer_open!
672
+
673
+ @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
674
+ with_continuation_timeout do
675
+ @last_basic_recover_ok = wait_on_continuations
676
+ end
677
+ raise_if_continuation_resulted_in_a_channel_error!
678
+
679
+ @last_basic_recover_ok
680
+ end
681
+
682
+ # Rejects or requeues a message.
683
+ #
684
+ # @param [Integer] delivery_tag Delivery tag obtained from delivery info
685
+ # @param [Boolean] requeue Should the message be requeued?
686
+ # @return [NilClass] nil
687
+ #
688
+ # @example Requeue a message
689
+ # conn = Bunny.new
690
+ # conn.start
691
+ #
692
+ # ch = conn.create_channel
693
+ # q.subscribe do |delivery_info, properties, payload|
694
+ # # requeue the message
695
+ # ch.basic_reject(delivery_info.delivery_tag, true)
696
+ # end
697
+ #
698
+ # @example Reject a message
699
+ # conn = Bunny.new
700
+ # conn.start
701
+ #
702
+ # ch = conn.create_channel
703
+ # q.subscribe do |delivery_info, properties, payload|
704
+ # # reject the message
705
+ # ch.basic_reject(delivery_info.delivery_tag, false)
706
+ # end
707
+ #
708
+ # @example Requeue a message fetched via basic.get
709
+ # conn = Bunny.new
710
+ # conn.start
711
+ #
712
+ # ch = conn.create_channel
713
+ # # we assume the queue exists and has messages
714
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
715
+ # ch.basic_reject(delivery_info.delivery_tag, true)
716
+ #
717
+ # @see Bunny::Channel#basic_nack
718
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
719
+ # @api public
720
+ def basic_reject(delivery_tag, requeue = false)
721
+ guarding_against_stale_delivery_tags(delivery_tag) do
722
+ raise_if_no_longer_open!
723
+ @connection.send_frame(AMQ::Protocol::Basic::Reject.encode(@id, delivery_tag, requeue))
724
+
725
+ nil
726
+ end
727
+ end
728
+
729
+ # Acknowledges a delivery (message).
730
+ #
731
+ # @param [Integer] delivery_tag Delivery tag obtained from delivery info
732
+ # @param [Boolean] multiple Should all deliveries up to this one be acknowledged?
733
+ # @return [NilClass] nil
734
+ #
735
+ # @example Ack a message
736
+ # conn = Bunny.new
737
+ # conn.start
738
+ #
739
+ # ch = conn.create_channel
740
+ # q.subscribe do |delivery_info, properties, payload|
741
+ # # requeue the message
742
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
743
+ # end
744
+ #
745
+ # @example Ack a message fetched via basic.get
746
+ # conn = Bunny.new
747
+ # conn.start
748
+ #
749
+ # ch = conn.create_channel
750
+ # # we assume the queue exists and has messages
751
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
752
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
753
+ #
754
+ # @example Ack multiple messages fetched via basic.get
755
+ # conn = Bunny.new
756
+ # conn.start
757
+ #
758
+ # ch = conn.create_channel
759
+ # # we assume the queue exists and has messages
760
+ # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
761
+ # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
762
+ # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
763
+ # # ack all fetched messages up to payload3
764
+ # ch.basic_ack(delivery_info.delivery_tag.to_i, true)
765
+ #
766
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
767
+ # @api public
768
+ def basic_ack(delivery_tag, multiple = false)
769
+ guarding_against_stale_delivery_tags(delivery_tag) do
770
+ raise_if_no_longer_open!
771
+ @connection.send_frame(AMQ::Protocol::Basic::Ack.encode(@id, delivery_tag, multiple))
772
+
773
+ nil
774
+ end
775
+ end
776
+
777
+ # Rejects or requeues messages just like {Bunny::Channel#basic_reject} but can do so
778
+ # with multiple messages at once.
779
+ #
780
+ # @param [Integer] delivery_tag Delivery tag obtained from delivery info
781
+ # @param [Boolean] requeue Should the message be requeued?
782
+ # @param [Boolean] multiple Should all deliveries up to this one be rejected/requeued?
783
+ # @return [NilClass] nil
784
+ #
785
+ # @example Requeue a message
786
+ # conn = Bunny.new
787
+ # conn.start
788
+ #
789
+ # ch = conn.create_channel
790
+ # q.subscribe do |delivery_info, properties, payload|
791
+ # # requeue the message
792
+ # ch.basic_nack(delivery_info.delivery_tag, false, true)
793
+ # end
794
+ #
795
+ # @example Reject a message
796
+ # conn = Bunny.new
797
+ # conn.start
798
+ #
799
+ # ch = conn.create_channel
800
+ # q.subscribe do |delivery_info, properties, payload|
801
+ # # requeue the message
802
+ # ch.basic_nack(delivery_info.delivery_tag)
803
+ # end
804
+ #
805
+ # @example Requeue a message fetched via basic.get
806
+ # conn = Bunny.new
807
+ # conn.start
808
+ #
809
+ # ch = conn.create_channel
810
+ # # we assume the queue exists and has messages
811
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
812
+ # ch.basic_nack(delivery_info.delivery_tag, false, true)
813
+ #
814
+ #
815
+ # @example Requeue multiple messages fetched via basic.get
816
+ # conn = Bunny.new
817
+ # conn.start
818
+ #
819
+ # ch = conn.create_channel
820
+ # # we assume the queue exists and has messages
821
+ # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
822
+ # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
823
+ # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
824
+ # # requeue all fetched messages up to payload3
825
+ # ch.basic_nack(delivery_info.delivery_tag, true, true)
826
+ #
827
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
828
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
829
+ # @api public
830
+ def basic_nack(delivery_tag, multiple = false, requeue = false)
831
+ guarding_against_stale_delivery_tags(delivery_tag) do
832
+ raise_if_no_longer_open!
833
+ @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
834
+ delivery_tag,
835
+ multiple,
836
+ requeue))
837
+
838
+ nil
839
+ end
840
+ end
841
+
842
+ # Registers a consumer for queue. Delivered messages will be handled with the block
843
+ # provided to this method.
844
+ #
845
+ # @param [String, Bunny::Queue] queue Queue to consume from
846
+ # @param [String] consumer_tag Consumer tag (unique identifier), generated by Bunny by default
847
+ # @param [Boolean] no_ack (false) If true, delivered messages will be automatically acknowledged.
848
+ # If false, manual acknowledgements will be necessary.
849
+ # @param [Boolean] exclusive (false) Should this consumer be exclusive?
850
+ # @param [Hash] arguments (nil) Optional arguments that may be used by RabbitMQ extensions, etc
851
+ #
852
+ # @return [AMQ::Protocol::Basic::ConsumeOk] RabbitMQ response
853
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
854
+ # @api public
855
+ def basic_consume(queue, consumer_tag = generate_consumer_tag, no_ack = false, exclusive = false, arguments = nil, &block)
856
+ raise_if_no_longer_open!
857
+ maybe_start_consumer_work_pool!
858
+
859
+ queue_name = if queue.respond_to?(:name)
860
+ queue.name
861
+ else
862
+ queue
863
+ end
864
+
865
+ # helps avoid race condition between basic.consume-ok and basic.deliver if there are messages
866
+ # in the queue already. MK.
867
+ if consumer_tag && consumer_tag.strip != AMQ::Protocol::EMPTY_STRING
868
+ add_consumer(queue_name, consumer_tag, no_ack, exclusive, arguments, &block)
869
+ end
870
+
871
+ @connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
872
+ queue_name,
873
+ consumer_tag,
874
+ false,
875
+ no_ack,
876
+ exclusive,
877
+ false,
878
+ arguments))
879
+
880
+ begin
881
+ with_continuation_timeout do
882
+ @last_basic_consume_ok = wait_on_continuations
883
+ end
884
+ rescue Exception => e
885
+ # if basic.consume-ok never arrives, unregister the proactively
886
+ # registered consumer. MK.
887
+ unregister_consumer(@last_basic_consume_ok.consumer_tag)
888
+
889
+ raise e
890
+ end
891
+
892
+ # in case there is another exclusive consumer and we get a channel.close
893
+ # response here. MK.
894
+ raise_if_channel_close!(@last_basic_consume_ok)
895
+
896
+ # covers server-generated consumer tags
897
+ add_consumer(queue_name, @last_basic_consume_ok.consumer_tag, no_ack, exclusive, arguments, &block)
898
+
899
+ @last_basic_consume_ok
900
+ end
901
+ alias consume basic_consume
902
+
903
+ # Registers a consumer for queue as {Bunny::Consumer} instance.
904
+ #
905
+ # @param [Bunny::Consumer] consumer Consumer to register. It should already have queue name, consumer tag
906
+ # and other attributes set.
907
+ #
908
+ # @return [AMQ::Protocol::Basic::ConsumeOk] RabbitMQ response
909
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
910
+ # @api public
911
+ def basic_consume_with(consumer)
912
+ raise_if_no_longer_open!
913
+ maybe_start_consumer_work_pool!
914
+
915
+ # helps avoid race condition between basic.consume-ok and basic.deliver if there are messages
916
+ # in the queue already. MK.
917
+ if consumer.consumer_tag && consumer.consumer_tag.strip != AMQ::Protocol::EMPTY_STRING
918
+ register_consumer(consumer.consumer_tag, consumer)
919
+ end
920
+
921
+ @connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
922
+ consumer.queue_name,
923
+ consumer.consumer_tag,
924
+ false,
925
+ consumer.no_ack,
926
+ consumer.exclusive,
927
+ false,
928
+ consumer.arguments))
929
+
930
+ begin
931
+ with_continuation_timeout do
932
+ @last_basic_consume_ok = wait_on_continuations
933
+ end
934
+ rescue Exception => e
935
+ # if basic.consume-ok never arrives, unregister the proactively
936
+ # registered consumer. MK.
937
+ unregister_consumer(@last_basic_consume_ok.consumer_tag)
938
+
939
+ raise e
940
+ end
941
+
942
+ # in case there is another exclusive consumer and we get a channel.close
943
+ # response here. MK.
944
+ raise_if_channel_close!(@last_basic_consume_ok)
945
+
946
+ # covers server-generated consumer tags
947
+ register_consumer(@last_basic_consume_ok.consumer_tag, consumer)
948
+
949
+ raise_if_continuation_resulted_in_a_channel_error!
950
+
951
+ @last_basic_consume_ok
952
+ end
953
+ alias consume_with basic_consume_with
954
+
955
+ # Removes a consumer. Messages for this consumer will no longer be delivered. If the queue
956
+ # it was on is auto-deleted and this consumer was the last one, the queue will be deleted.
957
+ #
958
+ # @param [String] consumer_tag Consumer tag (unique identifier) to cancel
959
+ #
960
+ # @return [AMQ::Protocol::Basic::CancelOk] RabbitMQ response
961
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
962
+ # @api public
963
+ def basic_cancel(consumer_tag)
964
+ @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
965
+
966
+ with_continuation_timeout do
967
+ @last_basic_cancel_ok = wait_on_continuations
968
+ end
969
+
970
+ # reduces thread usage for channels that don't have any
971
+ # consumers
972
+ @work_pool.shutdown(true) unless self.any_consumers?
973
+
974
+ @last_basic_cancel_ok
975
+ end
976
+
977
+ # @return [Boolean] true if there are consumers on this channel
978
+ # @api public
979
+ def any_consumers?
980
+ @consumer_mutex.synchronize { @consumers.any? }
981
+ end
982
+
983
+ # @endgroup
984
+
985
+
986
+ # @group Queue operations (queue.*)
987
+
988
+ # Declares a queue using queue.declare AMQP 0.9.1 method.
989
+ #
990
+ # @param [String] name The name of the queue or an empty string to let RabbitMQ generate a name.
991
+ # Note that LF and CR characters will be stripped from the value.
992
+ # @param [Hash] opts Queue properties
993
+ #
994
+ # @option opts [Boolean] durable (false) Should information about this queue be persisted to disk so that it
995
+ # can survive broker restarts? Typically set to true for long-lived queues.
996
+ # @option opts [Boolean] auto_delete (false) Should this queue be deleted when the last consumer is cancelled?
997
+ # @option opts [Boolean] exclusive (false) Should only this connection be able to use this queue?
998
+ # If true, the queue will be automatically deleted when this
999
+ # connection is closed
1000
+ # @option opts [Boolean] passive (false) If true, queue will be checked for existence. If it does not
1001
+ # exist, {Bunny::NotFound} will be raised.
1002
+ #
1003
+ # @return [AMQ::Protocol::Queue::DeclareOk] RabbitMQ response
1004
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
1005
+ # @api public
1006
+ def queue_declare(name, opts = {})
1007
+ raise_if_no_longer_open!
1008
+
1009
+ # strip trailing new line and carriage returns
1010
+ # just like RabbitMQ does
1011
+ safe_name = name.gsub(/[\r\n]/, "")
1012
+ @pending_queue_declare_name = safe_name
1013
+ @connection.send_frame(
1014
+ AMQ::Protocol::Queue::Declare.encode(@id,
1015
+ @pending_queue_declare_name,
1016
+ opts.fetch(:passive, false),
1017
+ opts.fetch(:durable, false),
1018
+ opts.fetch(:exclusive, false),
1019
+ opts.fetch(:auto_delete, false),
1020
+ false,
1021
+ opts[:arguments]))
1022
+
1023
+ begin
1024
+ with_continuation_timeout do
1025
+ @last_queue_declare_ok = wait_on_continuations
1026
+ end
1027
+ ensure
1028
+ # clear pending continuation context if it belongs to us
1029
+ @pending_queue_declare_name = nil if @pending_queue_declare_name == safe_name
1030
+ end
1031
+ raise_if_continuation_resulted_in_a_channel_error!
1032
+
1033
+ @last_queue_declare_ok
1034
+ end
1035
+
1036
+ # Deletes a queue using queue.delete AMQP 0.9.1 method
1037
+ #
1038
+ # @param [String] name Queue name
1039
+ # @param [Hash] opts Options
1040
+ #
1041
+ # @option opts [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers?
1042
+ # @option opts [Boolean] if_empty (false) Should this queue be deleted only if it has no messages?
1043
+ #
1044
+ # @return [AMQ::Protocol::Queue::DeleteOk] RabbitMQ response
1045
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
1046
+ # @api public
1047
+ def queue_delete(name, opts = {})
1048
+ raise_if_no_longer_open!
1049
+
1050
+ @connection.send_frame(AMQ::Protocol::Queue::Delete.encode(@id,
1051
+ name,
1052
+ opts[:if_unused],
1053
+ opts[:if_empty],
1054
+ false))
1055
+ with_continuation_timeout do
1056
+ @last_queue_delete_ok = wait_on_continuations
1057
+ end
1058
+ raise_if_continuation_resulted_in_a_channel_error!
1059
+
1060
+ @last_queue_delete_ok
1061
+ end
1062
+
1063
+ # Purges a queue (removes all messages from it) using queue.purge AMQP 0.9.1 method.
1064
+ #
1065
+ # @param [String] name Queue name
1066
+ #
1067
+ # @return [AMQ::Protocol::Queue::PurgeOk] RabbitMQ response
1068
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
1069
+ # @api public
1070
+ def queue_purge(name, opts = {})
1071
+ raise_if_no_longer_open!
1072
+
1073
+ @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
1074
+
1075
+ with_continuation_timeout do
1076
+ @last_queue_purge_ok = wait_on_continuations
1077
+ end
1078
+ raise_if_continuation_resulted_in_a_channel_error!
1079
+
1080
+ @last_queue_purge_ok
1081
+ end
1082
+
1083
+ # Binds a queue to an exchange using queue.bind AMQP 0.9.1 method
1084
+ #
1085
+ # @param [String] name Queue name
1086
+ # @param [String] exchange Exchange name
1087
+ # @param [Hash] opts Options
1088
+ #
1089
+ # @option opts [String] routing_key (nil) Routing key used for binding
1090
+ # @option opts [Hash] arguments ({}) Optional arguments
1091
+ #
1092
+ # @return [AMQ::Protocol::Queue::BindOk] RabbitMQ response
1093
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
1094
+ # @see http://rubybunny.info/articles/bindings.html Bindings guide
1095
+ # @api public
1096
+ def queue_bind(name, exchange, opts = {})
1097
+ raise_if_no_longer_open!
1098
+
1099
+ exchange_name = if exchange.respond_to?(:name)
1100
+ exchange.name
1101
+ else
1102
+ exchange
1103
+ end
1104
+
1105
+ @connection.send_frame(AMQ::Protocol::Queue::Bind.encode(@id,
1106
+ name,
1107
+ exchange_name,
1108
+ (opts[:routing_key] || opts[:key]),
1109
+ false,
1110
+ opts[:arguments]))
1111
+ with_continuation_timeout do
1112
+ @last_queue_bind_ok = wait_on_continuations
1113
+ end
1114
+
1115
+ raise_if_continuation_resulted_in_a_channel_error!
1116
+ @last_queue_bind_ok
1117
+ end
1118
+
1119
+ # Unbinds a queue from an exchange using queue.unbind AMQP 0.9.1 method
1120
+ #
1121
+ # @param [String] name Queue name
1122
+ # @param [String] exchange Exchange name
1123
+ # @param [Hash] opts Options
1124
+ #
1125
+ # @option opts [String] routing_key (nil) Routing key used for binding
1126
+ # @option opts [Hash] arguments ({}) Optional arguments
1127
+ #
1128
+ # @return [AMQ::Protocol::Queue::UnbindOk] RabbitMQ response
1129
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
1130
+ # @see http://rubybunny.info/articles/bindings.html Bindings guide
1131
+ # @api public
1132
+ def queue_unbind(name, exchange, opts = {})
1133
+ raise_if_no_longer_open!
1134
+
1135
+ exchange_name = if exchange.respond_to?(:name)
1136
+ exchange.name
1137
+ else
1138
+ exchange
1139
+ end
1140
+
1141
+ @connection.send_frame(AMQ::Protocol::Queue::Unbind.encode(@id,
1142
+ name,
1143
+ exchange_name,
1144
+ opts[:routing_key],
1145
+ opts[:arguments]))
1146
+ with_continuation_timeout do
1147
+ @last_queue_unbind_ok = wait_on_continuations
1148
+ end
1149
+
1150
+ raise_if_continuation_resulted_in_a_channel_error!
1151
+ @last_queue_unbind_ok
1152
+ end
1153
+
1154
+ # @endgroup
1155
+
1156
+
1157
+ # @group Exchange operations (exchange.*)
1158
+
1159
+ # Declares a exchange using exchange.declare AMQP 0.9.1 method.
1160
+ #
1161
+ # @param [String] name The name of the exchange. Note that LF and CR characters
1162
+ # will be stripped from the value.
1163
+ # @param [String,Symbol] type Exchange type, e.g. :fanout or :topic
1164
+ # @param [Hash] opts Exchange properties
1165
+ #
1166
+ # @option opts [Boolean] durable (false) Should information about this exchange be persisted to disk so that it
1167
+ # can survive broker restarts? Typically set to true for long-lived exchanges.
1168
+ # @option opts [Boolean] auto_delete (false) Should this exchange be deleted when it is no longer used?
1169
+ # @option opts [Boolean] passive (false) If true, exchange will be checked for existence. If it does not
1170
+ # exist, {Bunny::NotFound} will be raised.
1171
+ #
1172
+ # @return [AMQ::Protocol::Exchange::DeclareOk] RabbitMQ response
1173
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1174
+ # @api public
1175
+ def exchange_declare(name, type, opts = {})
1176
+ raise_if_no_longer_open!
1177
+
1178
+ # strip trailing new line and carriage returns
1179
+ # just like RabbitMQ does
1180
+ safe_name = name.gsub(/[\r\n]/, "")
1181
+ @connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
1182
+ safe_name,
1183
+ type.to_s,
1184
+ opts.fetch(:passive, false),
1185
+ opts.fetch(:durable, false),
1186
+ opts.fetch(:auto_delete, false),
1187
+ opts.fetch(:internal, false),
1188
+ false, # nowait
1189
+ opts[:arguments]))
1190
+ with_continuation_timeout do
1191
+ @last_exchange_declare_ok = wait_on_continuations
1192
+ end
1193
+
1194
+ raise_if_continuation_resulted_in_a_channel_error!
1195
+ @last_exchange_declare_ok
1196
+ end
1197
+
1198
+ # Deletes a exchange using exchange.delete AMQP 0.9.1 method
1199
+ #
1200
+ # @param [String] name Exchange name
1201
+ # @param [Hash] opts Options
1202
+ #
1203
+ # @option opts [Boolean] if_unused (false) Should this exchange be deleted only if it is no longer used
1204
+ #
1205
+ # @return [AMQ::Protocol::Exchange::DeleteOk] RabbitMQ response
1206
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1207
+ # @api public
1208
+ def exchange_delete(name, opts = {})
1209
+ raise_if_no_longer_open!
1210
+
1211
+ @connection.send_frame(AMQ::Protocol::Exchange::Delete.encode(@id,
1212
+ name,
1213
+ opts[:if_unused],
1214
+ false))
1215
+ with_continuation_timeout do
1216
+ @last_exchange_delete_ok = wait_on_continuations
1217
+ end
1218
+
1219
+ raise_if_continuation_resulted_in_a_channel_error!
1220
+ @last_exchange_delete_ok
1221
+ end
1222
+
1223
+ # Binds an exchange to another exchange using exchange.bind AMQP 0.9.1 extension
1224
+ # that RabbitMQ provides.
1225
+ #
1226
+ # @param [String] source Source exchange name
1227
+ # @param [String] destination Destination exchange name
1228
+ # @param [Hash] opts Options
1229
+ #
1230
+ # @option opts [String] routing_key (nil) Routing key used for binding
1231
+ # @option opts [Hash] arguments ({}) Optional arguments
1232
+ #
1233
+ # @return [AMQ::Protocol::Exchange::BindOk] RabbitMQ response
1234
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1235
+ # @see http://rubybunny.info/articles/bindings.html Bindings guide
1236
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1237
+ # @api public
1238
+ def exchange_bind(source, destination, opts = {})
1239
+ raise_if_no_longer_open!
1240
+
1241
+ source_name = if source.respond_to?(:name)
1242
+ source.name
1243
+ else
1244
+ source
1245
+ end
1246
+
1247
+ destination_name = if destination.respond_to?(:name)
1248
+ destination.name
1249
+ else
1250
+ destination
1251
+ end
1252
+
1253
+ @connection.send_frame(AMQ::Protocol::Exchange::Bind.encode(@id,
1254
+ destination_name,
1255
+ source_name,
1256
+ opts[:routing_key],
1257
+ false,
1258
+ opts[:arguments]))
1259
+ with_continuation_timeout do
1260
+ @last_exchange_bind_ok = wait_on_continuations
1261
+ end
1262
+
1263
+ raise_if_continuation_resulted_in_a_channel_error!
1264
+ @last_exchange_bind_ok
1265
+ end
1266
+
1267
+ # Unbinds an exchange from another exchange using exchange.unbind AMQP 0.9.1 extension
1268
+ # that RabbitMQ provides.
1269
+ #
1270
+ # @param [String] source Source exchange name
1271
+ # @param [String] destination Destination exchange name
1272
+ # @param [Hash] opts Options
1273
+ #
1274
+ # @option opts [String] routing_key (nil) Routing key used for binding
1275
+ # @option opts [Hash] arguments ({}) Optional arguments
1276
+ #
1277
+ # @return [AMQ::Protocol::Exchange::UnbindOk] RabbitMQ response
1278
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1279
+ # @see http://rubybunny.info/articles/bindings.html Bindings guide
1280
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1281
+ # @api public
1282
+ def exchange_unbind(source, destination, opts = {})
1283
+ raise_if_no_longer_open!
1284
+
1285
+ source_name = if source.respond_to?(:name)
1286
+ source.name
1287
+ else
1288
+ source
1289
+ end
1290
+
1291
+ destination_name = if destination.respond_to?(:name)
1292
+ destination.name
1293
+ else
1294
+ destination
1295
+ end
1296
+
1297
+ @connection.send_frame(AMQ::Protocol::Exchange::Unbind.encode(@id,
1298
+ destination_name,
1299
+ source_name,
1300
+ opts[:routing_key],
1301
+ false,
1302
+ opts[:arguments]))
1303
+ with_continuation_timeout do
1304
+ @last_exchange_unbind_ok = wait_on_continuations
1305
+ end
1306
+
1307
+ raise_if_continuation_resulted_in_a_channel_error!
1308
+ @last_exchange_unbind_ok
1309
+ end
1310
+
1311
+ # @endgroup
1312
+
1313
+
1314
+
1315
+ # @group Flow control (channel.*)
1316
+
1317
+ # Enables or disables message flow for the channel. When message flow is disabled,
1318
+ # no new messages will be delivered to consumers on this channel. This is typically
1319
+ # used by consumers that cannot keep up with the influx of messages.
1320
+ #
1321
+ # @note Recent (e.g. 2.8.x., 3.x) RabbitMQ will employ TCP/IP-level back pressure on publishers if it detects
1322
+ # that consumers do not keep up with them.
1323
+ #
1324
+ # @return [AMQ::Protocol::Channel::FlowOk] RabbitMQ response
1325
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
1326
+ # @api public
1327
+ def channel_flow(active)
1328
+ raise_if_no_longer_open!
1329
+
1330
+ @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
1331
+ with_continuation_timeout do
1332
+ @last_channel_flow_ok = wait_on_continuations
1333
+ end
1334
+ raise_if_continuation_resulted_in_a_channel_error!
1335
+
1336
+ @last_channel_flow_ok
1337
+ end
1338
+
1339
+ # @endgroup
1340
+
1341
+
1342
+
1343
+ # @group Transactions (tx.*)
1344
+
1345
+ # Puts the channel into transaction mode (starts a transaction)
1346
+ # @return [AMQ::Protocol::Tx::SelectOk] RabbitMQ response
1347
+ # @api public
1348
+ def tx_select
1349
+ raise_if_no_longer_open!
1350
+
1351
+ @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
1352
+ with_continuation_timeout do
1353
+ @last_tx_select_ok = wait_on_continuations
1354
+ end
1355
+ raise_if_continuation_resulted_in_a_channel_error!
1356
+ @tx_mode = true
1357
+
1358
+ @last_tx_select_ok
1359
+ end
1360
+
1361
+ # Commits current transaction
1362
+ # @return [AMQ::Protocol::Tx::CommitOk] RabbitMQ response
1363
+ # @api public
1364
+ def tx_commit
1365
+ raise_if_no_longer_open!
1366
+
1367
+ @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
1368
+ with_continuation_timeout do
1369
+ @last_tx_commit_ok = wait_on_continuations
1370
+ end
1371
+ raise_if_continuation_resulted_in_a_channel_error!
1372
+
1373
+ @last_tx_commit_ok
1374
+ end
1375
+
1376
+ # Rolls back current transaction
1377
+ # @return [AMQ::Protocol::Tx::RollbackOk] RabbitMQ response
1378
+ # @api public
1379
+ def tx_rollback
1380
+ raise_if_no_longer_open!
1381
+
1382
+ @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
1383
+ with_continuation_timeout do
1384
+ @last_tx_rollback_ok = wait_on_continuations
1385
+ end
1386
+ raise_if_continuation_resulted_in_a_channel_error!
1387
+
1388
+ @last_tx_rollback_ok
1389
+ end
1390
+
1391
+ # @return [Boolean] true if this channel has transactions enabled
1392
+ def using_tx?
1393
+ !!@tx_mode
1394
+ end
1395
+
1396
+ # @endgroup
1397
+
1398
+
1399
+
1400
+ # @group Publisher Confirms (confirm.*)
1401
+
1402
+ # @return [Boolean] true if this channel has Publisher Confirms enabled, false otherwise
1403
+ # @api public
1404
+ def using_publisher_confirmations?
1405
+ @next_publish_seq_no > 0
1406
+ end
1407
+ alias using_publisher_confirms? using_publisher_confirmations?
1408
+
1409
+ # Enables publisher confirms for the channel.
1410
+ # @return [AMQ::Protocol::Confirm::SelectOk] RabbitMQ response
1411
+ # @see #wait_for_confirms
1412
+ # @see #unconfirmed_set
1413
+ # @see #nacked_set
1414
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1415
+ # @api public
1416
+ def confirm_select(callback = nil)
1417
+ raise_if_no_longer_open!
1418
+
1419
+ if @next_publish_seq_no == 0
1420
+ @confirms_continuations = new_continuation
1421
+ @unconfirmed_set = Set.new
1422
+ @nacked_set = Set.new
1423
+ @next_publish_seq_no = 1
1424
+ @only_acks_received = true
1425
+ end
1426
+
1427
+ @confirms_callback = callback
1428
+
1429
+ @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
1430
+ with_continuation_timeout do
1431
+ @last_confirm_select_ok = wait_on_continuations
1432
+ end
1433
+ raise_if_continuation_resulted_in_a_channel_error!
1434
+ @last_confirm_select_ok
1435
+ end
1436
+
1437
+ # Blocks calling thread until confirms are received for all
1438
+ # currently unacknowledged published messages. Returns immediately
1439
+ # if there are no outstanding confirms.
1440
+ #
1441
+ # @return [Boolean] true if all messages were acknowledged positively since the last time this method was called, false otherwise
1442
+ # @see #confirm_select
1443
+ # @see #unconfirmed_set
1444
+ # @see #nacked_set
1445
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1446
+ # @api public
1447
+ def wait_for_confirms
1448
+ wait_on_confirms_continuations
1449
+ read_and_reset_only_acks_received
1450
+ end
1451
+
1452
+ # @endgroup
1453
+
1454
+
1455
+ # @group Misc
1456
+
1457
+ # Synchronizes given block using this channel's mutex.
1458
+ # @api public
1459
+ def synchronize(&block)
1460
+ @publishing_mutex.synchronize(&block)
1461
+ end
1462
+
1463
+ # Unique string supposed to be used as a consumer tag.
1464
+ #
1465
+ # @return [String] Unique string.
1466
+ # @api plugin
1467
+ def generate_consumer_tag(name = "bunny")
1468
+ "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
1469
+ end
1470
+
1471
+ # @endgroup
1472
+
1473
+
1474
+ #
1475
+ # Error Handilng
1476
+ #
1477
+
1478
+ # Defines a handler for errors that are not responses to a particular
1479
+ # operations (e.g. basic.ack, basic.reject, basic.nack).
1480
+ #
1481
+ # @api public
1482
+ def on_error(&block)
1483
+ @on_error = block
1484
+ end
1485
+
1486
+ # Defines a handler for uncaught exceptions in consumers
1487
+ # (e.g. delivered message handlers).
1488
+ #
1489
+ # @api public
1490
+ def on_uncaught_exception(&block)
1491
+ @uncaught_exception_handler = block
1492
+ end
1493
+
1494
+ #
1495
+ # Recovery
1496
+ #
1497
+
1498
+ # @group Network Failure Recovery
1499
+
1500
+ # Recovers basic.qos setting, exchanges, queues and consumers. Used by the Automatic Network Failure
1501
+ # Recovery feature.
1502
+ #
1503
+ # @api plugin
1504
+ def recover_from_network_failure
1505
+ @logger.debug { "Recovering channel #{@id} after network failure" }
1506
+ release_all_continuations
1507
+
1508
+ recover_prefetch_setting
1509
+ recover_confirm_mode
1510
+ recover_tx_mode
1511
+ recover_exchanges
1512
+ # this includes recovering bindings
1513
+ recover_queues
1514
+ recover_consumers
1515
+ increment_recoveries_counter
1516
+ end
1517
+
1518
+ # Recovers basic.qos setting. Used by the Automatic Network Failure
1519
+ # Recovery feature.
1520
+ #
1521
+ # @api plugin
1522
+ def recover_prefetch_setting
1523
+ basic_qos(@prefetch_count, @prefetch_global) if @prefetch_count
1524
+ end
1525
+
1526
+ # Recovers publisher confirms mode. Used by the Automatic Network Failure
1527
+ # Recovery feature.
1528
+ #
1529
+ # @api plugin
1530
+ def recover_confirm_mode
1531
+ if using_publisher_confirmations?
1532
+ @unconfirmed_set.clear
1533
+ @delivery_tag_offset = @next_publish_seq_no - 1
1534
+ confirm_select(@confirms_callback)
1535
+ end
1536
+ end
1537
+
1538
+ # Recovers transaction mode. Used by the Automatic Network Failure
1539
+ # Recovery feature.
1540
+ #
1541
+ # @api plugin
1542
+ def recover_tx_mode
1543
+ tx_select if @tx_mode
1544
+ end
1545
+
1546
+ # Recovers exchanges. Used by the Automatic Network Failure
1547
+ # Recovery feature.
1548
+ #
1549
+ # @api plugin
1550
+ def recover_exchanges
1551
+ @exchange_mutex.synchronize { @exchanges.values }.each do |x|
1552
+ x.recover_from_network_failure
1553
+ end
1554
+ end
1555
+
1556
+ # Recovers queues and bindings. Used by the Automatic Network Failure
1557
+ # Recovery feature.
1558
+ #
1559
+ # @api plugin
1560
+ def recover_queues
1561
+ @queue_mutex.synchronize { @queues.values }.each do |q|
1562
+ @logger.debug { "Recovering queue #{q.name}" }
1563
+ q.recover_from_network_failure
1564
+ end
1565
+ end
1566
+
1567
+ # Recovers consumers. Used by the Automatic Network Failure
1568
+ # Recovery feature.
1569
+ #
1570
+ # @api plugin
1571
+ def recover_consumers
1572
+ unless @consumers.empty?
1573
+ @work_pool = ConsumerWorkPool.new(@work_pool.size, @work_pool.abort_on_exception)
1574
+ @work_pool.start
1575
+ end
1576
+
1577
+ @consumer_mutex.synchronize { @consumers.values }.each do |c|
1578
+ c.recover_from_network_failure
1579
+ end
1580
+ end
1581
+
1582
+ # @private
1583
+ def increment_recoveries_counter
1584
+ @recoveries_counter.increment
1585
+ end
1586
+
1587
+ # @api public
1588
+ def recover_cancelled_consumers!
1589
+ @recover_cancelled_consumers = true
1590
+ end
1591
+
1592
+ # @api public
1593
+ def recovers_cancelled_consumers?
1594
+ !!@recover_cancelled_consumers
1595
+ end
1596
+
1597
+ # @endgroup
1598
+
1599
+
1600
+ # @return [String] Brief human-readable representation of the channel
1601
+ def to_s
1602
+ "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}> @open=#{open?}"
1603
+ end
1604
+
1605
+ def inspect
1606
+ to_s
1607
+ end
1608
+
1609
+
1610
+ #
1611
+ # Implementation
1612
+ #
1613
+
1614
+ # @private
1615
+ def with_continuation_timeout(&block)
1616
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout, &block)
1617
+ end
1618
+
1619
+ # @private
1620
+ def register_consumer(consumer_tag, consumer)
1621
+ @consumer_mutex.synchronize do
1622
+ @consumers[consumer_tag] = consumer
1623
+ end
1624
+ end
1625
+
1626
+ # @private
1627
+ def unregister_consumer(consumer_tag)
1628
+ @consumer_mutex.synchronize do
1629
+ @consumers.delete(consumer_tag)
1630
+ end
1631
+ end
1632
+
1633
+ # @private
1634
+ def add_consumer(queue, consumer_tag, no_ack, exclusive, arguments, &block)
1635
+ @consumer_mutex.synchronize do
1636
+ c = Consumer.new(self, queue, consumer_tag, no_ack, exclusive, arguments)
1637
+ c.on_delivery(&block) if block
1638
+ @consumers[consumer_tag] = c
1639
+ end
1640
+ end
1641
+
1642
+ # @private
1643
+ def pending_server_named_queue_declaration?
1644
+ @pending_queue_declare_name && @pending_queue_declare_name.empty?
1645
+ end
1646
+
1647
+ # @private
1648
+ def can_accept_queue_declare_ok?(method)
1649
+ @pending_queue_declare_name == method.queue ||
1650
+ pending_server_named_queue_declaration?
1651
+ end
1652
+
1653
+ # @private
1654
+ def handle_method(method)
1655
+ @logger.debug { "Channel#handle_frame on channel #{@id}: #{method.inspect}" }
1656
+ case method
1657
+ when AMQ::Protocol::Queue::DeclareOk then
1658
+ # safeguard against late arrivals of responses and
1659
+ # so on, see ruby-amqp/bunny#558
1660
+ if can_accept_queue_declare_ok?(method)
1661
+ @continuations.push(method)
1662
+ else
1663
+ if !pending_server_named_queue_declaration?
1664
+ # this response is for an outdated/overwritten
1665
+ # queue.declare, drop it
1666
+ @logger.warn "Received a queue.declare-ok response for a mismatching queue (#{method.queue} instead of #{@pending_queue_declare_name}) on channel #{@id} possibly due to a timeout, ignoring it"
1667
+ end
1668
+ end
1669
+ when AMQ::Protocol::Queue::DeleteOk then
1670
+ @continuations.push(method)
1671
+ when AMQ::Protocol::Queue::PurgeOk then
1672
+ @continuations.push(method)
1673
+ when AMQ::Protocol::Queue::BindOk then
1674
+ @continuations.push(method)
1675
+ when AMQ::Protocol::Queue::UnbindOk then
1676
+ @continuations.push(method)
1677
+ when AMQ::Protocol::Exchange::BindOk then
1678
+ @continuations.push(method)
1679
+ when AMQ::Protocol::Exchange::UnbindOk then
1680
+ @continuations.push(method)
1681
+ when AMQ::Protocol::Exchange::DeclareOk then
1682
+ @continuations.push(method)
1683
+ when AMQ::Protocol::Exchange::DeleteOk then
1684
+ @continuations.push(method)
1685
+ when AMQ::Protocol::Basic::QosOk then
1686
+ @continuations.push(method)
1687
+ when AMQ::Protocol::Basic::RecoverOk then
1688
+ @continuations.push(method)
1689
+ when AMQ::Protocol::Channel::FlowOk then
1690
+ @continuations.push(method)
1691
+ when AMQ::Protocol::Basic::ConsumeOk then
1692
+ @continuations.push(method)
1693
+ when AMQ::Protocol::Basic::Cancel then
1694
+ if consumer = @consumers[method.consumer_tag]
1695
+ @work_pool.submit do
1696
+ begin
1697
+ if recovers_cancelled_consumers?
1698
+ consumer.handle_cancellation(method)
1699
+ @logger.info "Automatically recovering cancelled consumer #{consumer.consumer_tag} on queue #{consumer.queue_name}"
1700
+
1701
+ consume_with(consumer)
1702
+ else
1703
+ @consumers.delete(method.consumer_tag)
1704
+ consumer.handle_cancellation(method)
1705
+ end
1706
+ rescue Exception => e
1707
+ @logger.error "Got exception when notifying consumer #{method.consumer_tag} about cancellation!"
1708
+ @uncaught_exception_handler.call(e, consumer) if @uncaught_exception_handler
1709
+ end
1710
+ end
1711
+ else
1712
+ @logger.warn "No consumer for tag #{method.consumer_tag} on channel #{@id}!"
1713
+ end
1714
+ when AMQ::Protocol::Basic::CancelOk then
1715
+ @continuations.push(method)
1716
+ unregister_consumer(method.consumer_tag)
1717
+ when AMQ::Protocol::Tx::SelectOk, AMQ::Protocol::Tx::CommitOk, AMQ::Protocol::Tx::RollbackOk then
1718
+ @continuations.push(method)
1719
+ when AMQ::Protocol::Tx::SelectOk then
1720
+ @continuations.push(method)
1721
+ when AMQ::Protocol::Confirm::SelectOk then
1722
+ @continuations.push(method)
1723
+ when AMQ::Protocol::Basic::Ack then
1724
+ handle_ack_or_nack(method.delivery_tag, method.multiple, false)
1725
+ when AMQ::Protocol::Basic::Nack then
1726
+ handle_ack_or_nack(method.delivery_tag, method.multiple, true)
1727
+ when AMQ::Protocol::Channel::Close then
1728
+ closed!
1729
+ @connection.send_frame(AMQ::Protocol::Channel::CloseOk.encode(@id))
1730
+
1731
+ # basic.ack, basic.reject, basic.nack. MK.
1732
+ if channel_level_exception_after_operation_that_has_no_response?(method)
1733
+ @on_error.call(self, method) if @on_error
1734
+ else
1735
+ @last_channel_error = instantiate_channel_level_exception(method)
1736
+ @continuations.push(method)
1737
+ end
1738
+
1739
+ when AMQ::Protocol::Channel::CloseOk then
1740
+ @continuations.push(method)
1741
+ else
1742
+ raise "Do not know how to handle #{method.inspect} in Bunny::Channel#handle_method"
1743
+ end
1744
+ end
1745
+
1746
+ # @private
1747
+ def channel_level_exception_after_operation_that_has_no_response?(method)
1748
+ method.reply_code == 406 && method.reply_text =~ /unknown delivery tag/
1749
+ end
1750
+
1751
+ # @private
1752
+ def handle_basic_get_ok(basic_get_ok, properties, content)
1753
+ basic_get_ok.delivery_tag = VersionedDeliveryTag.new(basic_get_ok.delivery_tag, @recoveries_counter.get)
1754
+ @basic_get_continuations.push([basic_get_ok, properties, content])
1755
+ end
1756
+
1757
+ # @private
1758
+ def handle_basic_get_empty(basic_get_empty)
1759
+ @basic_get_continuations.push([nil, nil, nil])
1760
+ end
1761
+
1762
+ # @private
1763
+ def handle_frameset(basic_deliver, properties, content)
1764
+ consumer = @consumers[basic_deliver.consumer_tag]
1765
+ if consumer
1766
+ @work_pool.submit do
1767
+ begin
1768
+ consumer.call(DeliveryInfo.new(basic_deliver, consumer, self), MessageProperties.new(properties), content)
1769
+ rescue StandardError => e
1770
+ @uncaught_exception_handler.call(e, consumer) if @uncaught_exception_handler
1771
+ end
1772
+ end
1773
+ else
1774
+ @logger.warn "No consumer for tag #{basic_deliver.consumer_tag} on channel #{@id}!"
1775
+ end
1776
+ end
1777
+
1778
+ # @private
1779
+ def handle_basic_return(basic_return, properties, content)
1780
+ x = find_exchange(basic_return.exchange)
1781
+
1782
+ if x
1783
+ x.handle_return(ReturnInfo.new(basic_return), MessageProperties.new(properties), content)
1784
+ else
1785
+ @logger.warn "Exchange #{basic_return.exchange} is not in channel #{@id}'s cache! Dropping returned message!"
1786
+ end
1787
+ end
1788
+
1789
+ # @private
1790
+ def handle_ack_or_nack(delivery_tag_before_offset, multiple, nack)
1791
+ delivery_tag = delivery_tag_before_offset + @delivery_tag_offset
1792
+ confirmed_range_start = multiple ? @delivery_tag_offset + @unconfirmed_set.min : delivery_tag
1793
+ confirmed_range_end = delivery_tag
1794
+ confirmed_range = (confirmed_range_start..confirmed_range_end)
1795
+
1796
+ @unconfirmed_set_mutex.synchronize do
1797
+ if nack
1798
+ @nacked_set.merge(@unconfirmed_set & confirmed_range)
1799
+ end
1800
+
1801
+ @unconfirmed_set.subtract(confirmed_range)
1802
+
1803
+ @only_acks_received = (@only_acks_received && !nack)
1804
+
1805
+ @confirms_continuations.push(true) if @unconfirmed_set.empty?
1806
+
1807
+ if @confirms_callback
1808
+ confirmed_range.each { |tag| @confirms_callback.call(tag, false, nack) }
1809
+ end
1810
+ end
1811
+ end
1812
+
1813
+ # @private
1814
+ def wait_on_continuations
1815
+ if @connection.threaded
1816
+ t = Thread.current
1817
+ @threads_waiting_on_continuations << t
1818
+
1819
+ begin
1820
+ @continuations.poll(@connection.continuation_timeout)
1821
+ ensure
1822
+ @threads_waiting_on_continuations.delete(t)
1823
+ end
1824
+ else
1825
+ connection.reader_loop.run_once until @continuations.length > 0
1826
+
1827
+ @continuations.pop
1828
+ end
1829
+ end
1830
+
1831
+ # @private
1832
+ def wait_on_basic_get_continuations
1833
+ if @connection.threaded
1834
+ t = Thread.current
1835
+ @threads_waiting_on_basic_get_continuations << t
1836
+
1837
+ begin
1838
+ @basic_get_continuations.poll(@connection.continuation_timeout)
1839
+ ensure
1840
+ @threads_waiting_on_basic_get_continuations.delete(t)
1841
+ end
1842
+ else
1843
+ connection.reader_loop.run_once until @basic_get_continuations.length > 0
1844
+
1845
+ @basic_get_continuations.pop
1846
+ end
1847
+ end
1848
+
1849
+ # @private
1850
+ def wait_on_confirms_continuations
1851
+ raise_if_no_longer_open!
1852
+
1853
+ if @connection.threaded
1854
+ t = Thread.current
1855
+ @threads_waiting_on_confirms_continuations << t
1856
+
1857
+ begin
1858
+ while @unconfirmed_set_mutex.synchronize { !@unconfirmed_set.empty? }
1859
+ @confirms_continuations.poll(@connection.continuation_timeout)
1860
+ end
1861
+ ensure
1862
+ @threads_waiting_on_confirms_continuations.delete(t)
1863
+ end
1864
+ else
1865
+ unless @unconfirmed_set.empty?
1866
+ connection.reader_loop.run_once until @confirms_continuations.length > 0
1867
+ @confirms_continuations.pop
1868
+ end
1869
+ end
1870
+ end
1871
+
1872
+ # @private
1873
+ def read_and_reset_only_acks_received
1874
+ @unconfirmed_set_mutex.synchronize do
1875
+ result = @only_acks_received
1876
+ @only_acks_received = true
1877
+ result
1878
+ end
1879
+ end
1880
+
1881
+
1882
+ # Releases all continuations. Used by automatic network recovery.
1883
+ # @private
1884
+ def release_all_continuations
1885
+ @threads_waiting_on_confirms_continuations.each do |t|
1886
+ t.run
1887
+ end
1888
+ @threads_waiting_on_continuations.each do |t|
1889
+ t.run
1890
+ end
1891
+ @threads_waiting_on_basic_get_continuations.each do |t|
1892
+ t.run
1893
+ end
1894
+
1895
+ self.reset_continuations
1896
+ end
1897
+
1898
+ # Starts consumer work pool. Lazily called by #basic_consume to avoid creating new threads
1899
+ # that won't do any real work for channels that do not register consumers (e.g. only used for
1900
+ # publishing). MK.
1901
+ # @private
1902
+ def maybe_start_consumer_work_pool!
1903
+ if @work_pool && !@work_pool.running?
1904
+ @work_pool.start
1905
+ end
1906
+ end
1907
+
1908
+ # @private
1909
+ def maybe_pause_consumer_work_pool!
1910
+ @work_pool.pause if @work_pool && @work_pool.running?
1911
+ end
1912
+
1913
+ # @private
1914
+ def maybe_kill_consumer_work_pool!
1915
+ if @work_pool && @work_pool.running?
1916
+ @work_pool.kill
1917
+ end
1918
+ end
1919
+
1920
+ # @private
1921
+ def read_next_frame(options = {})
1922
+ @connection.read_next_frame(options = {})
1923
+ end
1924
+
1925
+ # @private
1926
+ def deregister_queue(queue)
1927
+ @queue_mutex.synchronize { @queues.delete(queue.name) }
1928
+ end
1929
+
1930
+ # @private
1931
+ def deregister_queue_named(name)
1932
+ @queue_mutex.synchronize { @queues.delete(name) }
1933
+ end
1934
+
1935
+ # @private
1936
+ def register_queue(queue)
1937
+ @queue_mutex.synchronize { @queues[queue.name] = queue }
1938
+ end
1939
+
1940
+ # @private
1941
+ def find_queue(name)
1942
+ @queue_mutex.synchronize { @queues[name] }
1943
+ end
1944
+
1945
+ # @private
1946
+ def deregister_exchange(exchange)
1947
+ @exchange_mutex.synchronize { @exchanges.delete(exchange.name) }
1948
+ end
1949
+
1950
+ # @private
1951
+ def register_exchange(exchange)
1952
+ @exchange_mutex.synchronize { @exchanges[exchange.name] = exchange }
1953
+ end
1954
+
1955
+ # @private
1956
+ def find_exchange(name)
1957
+ @exchange_mutex.synchronize { @exchanges[name] }
1958
+ end
1959
+
1960
+ protected
1961
+
1962
+ # @private
1963
+ def closed!
1964
+ @status = :closed
1965
+ @work_pool.shutdown
1966
+ @connection.release_channel_id(@id)
1967
+ end
1968
+
1969
+ # @private
1970
+ def instantiate_channel_level_exception(frame)
1971
+ case frame
1972
+ when AMQ::Protocol::Channel::Close then
1973
+ klass = case frame.reply_code
1974
+ when 403 then
1975
+ AccessRefused
1976
+ when 404 then
1977
+ NotFound
1978
+ when 405 then
1979
+ ResourceLocked
1980
+ when 406 then
1981
+ PreconditionFailed
1982
+ else
1983
+ ChannelLevelException
1984
+ end
1985
+
1986
+ klass.new(frame.reply_text, self, frame)
1987
+ end
1988
+ end
1989
+
1990
+ # @private
1991
+ def raise_if_continuation_resulted_in_a_channel_error!
1992
+ raise @last_channel_error if @last_channel_error
1993
+ end
1994
+
1995
+ # @private
1996
+ def raise_if_no_longer_open!
1997
+ if closed?
1998
+ if @last_channel_error
1999
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}, closed due to a server-reported channel error: #{@last_channel_error.message}", self)
2000
+ else
2001
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}", self)
2002
+ end
2003
+ end
2004
+ end
2005
+
2006
+ # @private
2007
+ def raise_if_channel_close!(method)
2008
+ if method && method.is_a?(AMQ::Protocol::Channel::Close)
2009
+ # basic.ack, basic.reject, basic.nack. MK.
2010
+ if channel_level_exception_after_operation_that_has_no_response?(method)
2011
+ @on_error.call(self, method) if @on_error
2012
+ else
2013
+ @last_channel_error = instantiate_channel_level_exception(method)
2014
+ raise @last_channel_error
2015
+ end
2016
+ end
2017
+ end
2018
+
2019
+ # @private
2020
+ def reset_continuations
2021
+ @continuations = new_continuation
2022
+ @confirms_continuations = new_continuation
2023
+ @basic_get_continuations = new_continuation
2024
+ end
2025
+
2026
+
2027
+ if defined?(JRUBY_VERSION)
2028
+ # @private
2029
+ def new_continuation
2030
+ Concurrent::LinkedContinuationQueue.new
2031
+ end
2032
+ else
2033
+ # @private
2034
+ def new_continuation
2035
+ Concurrent::ContinuationQueue.new
2036
+ end
2037
+ end # if defined?
2038
+
2039
+ # @private
2040
+ def guarding_against_stale_delivery_tags(tag, &block)
2041
+ case tag
2042
+ # if a fixnum was passed, execute unconditionally. MK.
2043
+ when Integer then
2044
+ block.call
2045
+ # versioned delivery tags should be checked to avoid
2046
+ # sending out stale (invalid) tags after channel was reopened
2047
+ # during network failure recovery. MK.
2048
+ when VersionedDeliveryTag then
2049
+ if !tag.stale?(@recoveries_counter.get)
2050
+ block.call
2051
+ end
2052
+ end
2053
+ end
2054
+ end # Channel
2055
+ end # Bunny