garaio_bunny 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
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