bunny 0.9.0.pre2 → 0.9.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -4
- data/ChangeLog.md +94 -0
- data/LICENSE +1 -1
- data/bunny.gemspec +1 -0
- data/examples/connection/unknown_host.rb +13 -0
- data/lib/bunny/channel.rb +173 -38
- data/lib/bunny/channel_id_allocator.rb +3 -3
- data/lib/bunny/consumer.rb +15 -8
- data/lib/bunny/queue.rb +26 -4
- data/lib/bunny/session.rb +33 -22
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/connection_spec.rb +6 -0
- data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +83 -0
- data/spec/higher_level_api/integration/publisher_confirms_spec.rb +39 -0
- metadata +9 -4
data/.yardopts
CHANGED
data/ChangeLog.md
CHANGED
@@ -1,3 +1,97 @@
|
|
1
|
+
## Changes between Bunny 0.9.0.pre3 and 0.9.0.pre4
|
2
|
+
|
3
|
+
No changes yet.
|
4
|
+
|
5
|
+
|
6
|
+
## Changes between Bunny 0.9.0.pre2 and 0.9.0.pre3
|
7
|
+
|
8
|
+
### Client Capabilities
|
9
|
+
|
10
|
+
Bunny now correctly lists RabbitMQ extensions it currently supports in client capabilities:
|
11
|
+
|
12
|
+
* `basic.nack`
|
13
|
+
* exchange-to-exchange bindings
|
14
|
+
* consumer cancellation notifications
|
15
|
+
* publisher confirms
|
16
|
+
|
17
|
+
### Publisher Confirms Support
|
18
|
+
|
19
|
+
[Lightweight Publisher Confirms](http://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms/) is a
|
20
|
+
RabbitMQ feature that lets publishers keep track of message routing without adding
|
21
|
+
noticeable throughput degradation as it is the case with AMQP 0.9.1 transactions.
|
22
|
+
|
23
|
+
Bunny `0.9.0.pre3` supports publisher confirms. Publisher confirms are enabled per channel,
|
24
|
+
using the `Bunny::Channel#confirm_select` method. `Bunny::Channel#wait_for_confirms` is a method
|
25
|
+
that blocks current thread until the client gets confirmations for all unconfirmed published
|
26
|
+
messages:
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
ch = connection.create_channel
|
30
|
+
ch.confirm_select
|
31
|
+
|
32
|
+
ch.using_publisher_confirmations? # => true
|
33
|
+
|
34
|
+
q = ch.queue("", :exclusive => true)
|
35
|
+
x = ch.default_exchange
|
36
|
+
|
37
|
+
5000.times do
|
38
|
+
x.publish("xyzzy", :routing_key => q.name)
|
39
|
+
end
|
40
|
+
|
41
|
+
ch.next_publish_seq_no.should == 5001
|
42
|
+
ch.wait_for_confirms # waits until all 5000 published messages are acknowledged by RabbitMQ
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
### Consumers as Objects
|
47
|
+
|
48
|
+
It is now possible to register a consumer as an object instead
|
49
|
+
of a block. Consumers that are class instances support cancellation
|
50
|
+
notifications (e.g. when a queue they're registered with is deleted).
|
51
|
+
|
52
|
+
To support this, Bunny introduces two new methods: `Bunny::Channel#basic_consume_with`
|
53
|
+
and `Bunny::Queue#subscribe_with`, that operate on consumer objects. Objects are
|
54
|
+
supposed to respond to three selectors:
|
55
|
+
|
56
|
+
* `:handle_delivery` with 3 arguments
|
57
|
+
* `:handle_cancellation` with 1 argument
|
58
|
+
* `:consumer_tag=` with 1 argument
|
59
|
+
|
60
|
+
An example:
|
61
|
+
|
62
|
+
``` ruby
|
63
|
+
class ExampleConsumer < Bunny::Consumer
|
64
|
+
def cancelled?
|
65
|
+
@cancelled
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_cancellation(_)
|
69
|
+
@cancelled = true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# "high-level" API
|
74
|
+
ch1 = connection.create_channel
|
75
|
+
q1 = ch1.queue("", :auto_delete => true)
|
76
|
+
|
77
|
+
consumer = ExampleConsumer.new(ch1, q)
|
78
|
+
q1.subscribe_with(consumer)
|
79
|
+
|
80
|
+
# "low-level" API
|
81
|
+
ch2 = connection.create_channel
|
82
|
+
q1 = ch2.queue("", :auto_delete => true)
|
83
|
+
|
84
|
+
consumer = ExampleConsumer.new(ch2, q)
|
85
|
+
ch2.basic_consume_with.(consumer)
|
86
|
+
```
|
87
|
+
|
88
|
+
### RABBITMQ_URL ENV variable support
|
89
|
+
|
90
|
+
If `RABBITMQ_URL` environment variable is set, Bunny will assume
|
91
|
+
it contains a valid amqp URI string and will use it. This is convenient
|
92
|
+
with some PaaS technologies such as Heroku.
|
93
|
+
|
94
|
+
|
1
95
|
## Changes between Bunny 0.9.0.pre1 and 0.9.0.pre2
|
2
96
|
|
3
97
|
### Change Bunny::Queue#pop default for :ack to false
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2009 –
|
1
|
+
Copyright (c) 2009 – 2013 Chris Duncan, Jakub Stastny aka botanicus,
|
2
2
|
Michael S. Klishin, Eric Lindvall, Stefan Kaes and contributors.
|
3
3
|
|
4
4
|
Permission is hereby granted, free of charge, to any person obtaining
|
data/bunny.gemspec
CHANGED
@@ -10,6 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.homepage = "http://github.com/ruby-amqp/bunny"
|
11
11
|
s.summary = "Easy to use synchronous Ruby client for RabbitMQ"
|
12
12
|
s.description = "Easy to use synchronous Ruby client for RabbitMQ"
|
13
|
+
s.license = "MIT"
|
13
14
|
|
14
15
|
# Sorted alphabetically.
|
15
16
|
s.authors = [
|
data/lib/bunny/channel.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "thread"
|
2
|
-
require "
|
2
|
+
require "set"
|
3
3
|
|
4
4
|
require "bunny/consumer_work_pool"
|
5
5
|
|
@@ -18,6 +18,7 @@ module Bunny
|
|
18
18
|
#
|
19
19
|
|
20
20
|
attr_accessor :id, :connection, :status, :work_pool
|
21
|
+
attr_reader :next_publish_seq_no
|
21
22
|
|
22
23
|
|
23
24
|
def initialize(connection = nil, id = nil, work_pool = ConsumerWorkPool.new(1))
|
@@ -33,12 +34,16 @@ module Bunny
|
|
33
34
|
@work_pool = work_pool
|
34
35
|
|
35
36
|
# synchronizes frameset delivery. MK.
|
36
|
-
@
|
37
|
-
@consumer_mutex
|
37
|
+
@publishing_mutex = Mutex.new
|
38
|
+
@consumer_mutex = Mutex.new
|
38
39
|
|
39
|
-
@
|
40
|
-
|
40
|
+
@unconfirmed_set_mutex = Mutex.new
|
41
|
+
|
42
|
+
@continuations = ::Queue.new
|
43
|
+
@confirms_continuations = ::Queue.new
|
41
44
|
|
45
|
+
@next_publish_seq_no = 0
|
46
|
+
end
|
42
47
|
|
43
48
|
def open
|
44
49
|
@connection.open_channel(self)
|
@@ -146,6 +151,9 @@ module Bunny
|
|
146
151
|
@default_error_handler = block
|
147
152
|
end
|
148
153
|
|
154
|
+
def using_publisher_confirmations?
|
155
|
+
@next_publish_seq_no > 0
|
156
|
+
end
|
149
157
|
|
150
158
|
#
|
151
159
|
# Lower-level API, exposes protocol operations as they are defined in the protocol,
|
@@ -165,7 +173,20 @@ module Bunny
|
|
165
173
|
|
166
174
|
meta = { :priority => 0, :delivery_mode => 2, :content_type => "application/octet-stream" }.
|
167
175
|
merge(opts)
|
168
|
-
|
176
|
+
|
177
|
+
if @next_publish_seq_no > 0
|
178
|
+
@unconfirmed_set.add(@next_publish_seq_no)
|
179
|
+
@next_publish_seq_no += 1
|
180
|
+
end
|
181
|
+
|
182
|
+
@connection.send_frameset(AMQ::Protocol::Basic::Publish.encode(@id,
|
183
|
+
payload,
|
184
|
+
meta,
|
185
|
+
exchange_name,
|
186
|
+
routing_key,
|
187
|
+
meta[:mandatory],
|
188
|
+
false,
|
189
|
+
@connection.frame_max), self)
|
169
190
|
|
170
191
|
self
|
171
192
|
end
|
@@ -222,7 +243,10 @@ module Bunny
|
|
222
243
|
|
223
244
|
def basic_nack(delivery_tag, requeue, multiple = false)
|
224
245
|
raise_if_no_longer_open!
|
225
|
-
@connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
|
246
|
+
@connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
|
247
|
+
delivery_tag,
|
248
|
+
requeue,
|
249
|
+
multiple))
|
226
250
|
|
227
251
|
nil
|
228
252
|
end
|
@@ -237,21 +261,54 @@ module Bunny
|
|
237
261
|
queue
|
238
262
|
end
|
239
263
|
|
240
|
-
@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
|
264
|
+
@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
|
265
|
+
queue_name,
|
266
|
+
consumer_tag,
|
267
|
+
false,
|
268
|
+
no_ack,
|
269
|
+
exclusive,
|
270
|
+
false,
|
271
|
+
arguments))
|
241
272
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
242
273
|
@last_basic_consume_ok = @continuations.pop
|
243
274
|
end
|
244
275
|
|
245
276
|
@consumer_mutex.synchronize do
|
246
|
-
|
277
|
+
# make sure to use consumer tag from basic.consume-ok in case it was
|
278
|
+
# server-generated
|
279
|
+
c = Consumer.new(self, queue, @last_basic_consume_ok.consumer_tag, no_ack, exclusive, arguments)
|
247
280
|
c.on_delivery(&block) if block
|
248
|
-
|
249
281
|
@consumers[@last_basic_consume_ok.consumer_tag] = c
|
250
282
|
end
|
251
283
|
|
252
284
|
@last_basic_consume_ok
|
253
285
|
end
|
254
286
|
|
287
|
+
def basic_consume_with(consumer)
|
288
|
+
raise_if_no_longer_open!
|
289
|
+
maybe_start_consumer_work_pool!
|
290
|
+
|
291
|
+
@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
|
292
|
+
consumer.queue_name,
|
293
|
+
consumer.consumer_tag,
|
294
|
+
false,
|
295
|
+
consumer.no_ack,
|
296
|
+
consumer.exclusive,
|
297
|
+
false,
|
298
|
+
consumer.arguments))
|
299
|
+
Bunny::Timer.timeout(1, ClientTimeout) do
|
300
|
+
@last_basic_consume_ok = @continuations.pop
|
301
|
+
end
|
302
|
+
|
303
|
+
@consumer_mutex.synchronize do
|
304
|
+
# update the tag in case it was server-generated
|
305
|
+
consumer.consumer_tag = @last_basic_consume_ok.consumer_tag
|
306
|
+
@consumers[@last_basic_consume_ok.consumer_tag] = consumer
|
307
|
+
end
|
308
|
+
|
309
|
+
@last_basic_consume_ok
|
310
|
+
end
|
311
|
+
|
255
312
|
def basic_cancel(consumer_tag)
|
256
313
|
@connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
|
257
314
|
|
@@ -268,7 +325,14 @@ module Bunny
|
|
268
325
|
def queue_declare(name, opts = {})
|
269
326
|
raise_if_no_longer_open!
|
270
327
|
|
271
|
-
@connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
|
328
|
+
@connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
|
329
|
+
name,
|
330
|
+
opts.fetch(:passive, false),
|
331
|
+
opts.fetch(:durable, false),
|
332
|
+
opts.fetch(:exclusive, false),
|
333
|
+
opts.fetch(:auto_delete, false),
|
334
|
+
false,
|
335
|
+
opts[:arguments]))
|
272
336
|
@last_queue_declare_ok = @continuations.pop
|
273
337
|
|
274
338
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -279,7 +343,11 @@ module Bunny
|
|
279
343
|
def queue_delete(name, opts = {})
|
280
344
|
raise_if_no_longer_open!
|
281
345
|
|
282
|
-
@connection.send_frame(AMQ::Protocol::Queue::Delete.encode(@id,
|
346
|
+
@connection.send_frame(AMQ::Protocol::Queue::Delete.encode(@id,
|
347
|
+
name,
|
348
|
+
opts[:if_unused],
|
349
|
+
opts[:if_empty],
|
350
|
+
false))
|
283
351
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
284
352
|
@last_queue_delete_ok = @continuations.pop
|
285
353
|
end
|
@@ -310,7 +378,12 @@ module Bunny
|
|
310
378
|
exchange
|
311
379
|
end
|
312
380
|
|
313
|
-
@connection.send_frame(AMQ::Protocol::Queue::Bind.encode(@id,
|
381
|
+
@connection.send_frame(AMQ::Protocol::Queue::Bind.encode(@id,
|
382
|
+
name,
|
383
|
+
exchange_name,
|
384
|
+
opts[:routing_key],
|
385
|
+
false,
|
386
|
+
opts[:arguments]))
|
314
387
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
315
388
|
@last_queue_bind_ok = @continuations.pop
|
316
389
|
end
|
@@ -328,7 +401,11 @@ module Bunny
|
|
328
401
|
exchange
|
329
402
|
end
|
330
403
|
|
331
|
-
@connection.send_frame(AMQ::Protocol::Queue::Unbind.encode(@id,
|
404
|
+
@connection.send_frame(AMQ::Protocol::Queue::Unbind.encode(@id,
|
405
|
+
name,
|
406
|
+
exchange_name,
|
407
|
+
opts[:routing_key],
|
408
|
+
opts[:arguments]))
|
332
409
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
333
410
|
@last_queue_unbind_ok = @continuations.pop
|
334
411
|
end
|
@@ -343,7 +420,15 @@ module Bunny
|
|
343
420
|
def exchange_declare(name, type, opts = {})
|
344
421
|
raise_if_no_longer_open!
|
345
422
|
|
346
|
-
@connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
|
423
|
+
@connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
|
424
|
+
name,
|
425
|
+
type.to_s,
|
426
|
+
opts.fetch(:passive, false),
|
427
|
+
opts.fetch(:durable, false),
|
428
|
+
opts.fetch(:auto_delete, false),
|
429
|
+
false,
|
430
|
+
false,
|
431
|
+
opts[:arguments]))
|
347
432
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
348
433
|
@last_exchange_declare_ok = @continuations.pop
|
349
434
|
end
|
@@ -355,7 +440,10 @@ module Bunny
|
|
355
440
|
def exchange_delete(name, opts = {})
|
356
441
|
raise_if_no_longer_open!
|
357
442
|
|
358
|
-
@connection.send_frame(AMQ::Protocol::Exchange::Delete.encode(@id,
|
443
|
+
@connection.send_frame(AMQ::Protocol::Exchange::Delete.encode(@id,
|
444
|
+
name,
|
445
|
+
opts[:if_unused],
|
446
|
+
false))
|
359
447
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
360
448
|
@last_exchange_delete_ok = @continuations.pop
|
361
449
|
end
|
@@ -368,18 +456,23 @@ module Bunny
|
|
368
456
|
raise_if_no_longer_open!
|
369
457
|
|
370
458
|
source_name = if source.respond_to?(:name)
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
459
|
+
source.name
|
460
|
+
else
|
461
|
+
source
|
462
|
+
end
|
375
463
|
|
376
464
|
destination_name = if destination.respond_to?(:name)
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
@connection.send_frame(AMQ::Protocol::Exchange::Bind.encode(@id,
|
465
|
+
destination.name
|
466
|
+
else
|
467
|
+
destination
|
468
|
+
end
|
469
|
+
|
470
|
+
@connection.send_frame(AMQ::Protocol::Exchange::Bind.encode(@id,
|
471
|
+
destination_name,
|
472
|
+
source_name,
|
473
|
+
opts[:routing_key],
|
474
|
+
false,
|
475
|
+
opts[:arguments]))
|
383
476
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
384
477
|
@last_exchange_bind_ok = @continuations.pop
|
385
478
|
end
|
@@ -392,18 +485,23 @@ module Bunny
|
|
392
485
|
raise_if_no_longer_open!
|
393
486
|
|
394
487
|
source_name = if source.respond_to?(:name)
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
488
|
+
source.name
|
489
|
+
else
|
490
|
+
source
|
491
|
+
end
|
399
492
|
|
400
493
|
destination_name = if destination.respond_to?(:name)
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
@connection.send_frame(AMQ::Protocol::Exchange::Unbind.encode(@id,
|
494
|
+
destination.name
|
495
|
+
else
|
496
|
+
destination
|
497
|
+
end
|
498
|
+
|
499
|
+
@connection.send_frame(AMQ::Protocol::Exchange::Unbind.encode(@id,
|
500
|
+
destination_name,
|
501
|
+
source_name,
|
502
|
+
opts[:routing_key],
|
503
|
+
false,
|
504
|
+
opts[:arguments]))
|
407
505
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
408
506
|
@last_exchange_unbind_ok = @continuations.pop
|
409
507
|
end
|
@@ -469,15 +567,26 @@ module Bunny
|
|
469
567
|
def confirm_select
|
470
568
|
raise_if_no_longer_open!
|
471
569
|
|
570
|
+
if @next_publish_seq_no == 0
|
571
|
+
@confirms_continuations = []
|
572
|
+
@unconfirmed_set = Set.new
|
573
|
+
@next_publish_seq_no = 1
|
574
|
+
end
|
575
|
+
|
472
576
|
@connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
|
473
577
|
Bunny::Timer.timeout(1, ClientTimeout) do
|
474
578
|
@last_confirm_select_ok = @continuations.pop
|
475
579
|
end
|
476
580
|
raise_if_continuation_resulted_in_a_channel_error!
|
477
|
-
|
478
581
|
@last_confirm_select_ok
|
479
582
|
end
|
480
583
|
|
584
|
+
def wait_for_confirms
|
585
|
+
@only_acks_received = true
|
586
|
+
@confirms_continuations.pop
|
587
|
+
|
588
|
+
@only_acks_received
|
589
|
+
end
|
481
590
|
|
482
591
|
#
|
483
592
|
# Implementation
|
@@ -512,6 +621,12 @@ module Bunny
|
|
512
621
|
@continuations.push(method)
|
513
622
|
when AMQ::Protocol::Basic::ConsumeOk then
|
514
623
|
@continuations.push(method)
|
624
|
+
when AMQ::Protocol::Basic::Cancel then
|
625
|
+
if consumer = @consumers[method.consumer_tag]
|
626
|
+
consumer.handle_cancellation(method)
|
627
|
+
end
|
628
|
+
|
629
|
+
@consumers.delete(method.consumer_tag)
|
515
630
|
when AMQ::Protocol::Basic::CancelOk then
|
516
631
|
@continuations.push(method)
|
517
632
|
@consumers.delete(method.consumer_tag)
|
@@ -521,6 +636,12 @@ module Bunny
|
|
521
636
|
@continuations.push(method)
|
522
637
|
when AMQ::Protocol::Confirm::SelectOk then
|
523
638
|
@continuations.push(method)
|
639
|
+
when AMQ::Protocol::Basic::Ack then
|
640
|
+
# TODO: implement confirm listeners
|
641
|
+
handle_ack_or_nack(method.delivery_tag, method.multiple, false)
|
642
|
+
when AMQ::Protocol::Basic::Nack then
|
643
|
+
# TODO: implement confirm listeners
|
644
|
+
handle_ack_or_nack(method.delivery_tag, method.multiple, true)
|
524
645
|
when AMQ::Protocol::Channel::Close then
|
525
646
|
# puts "Exception on channel #{@id}: #{method.reply_code} #{method.reply_text}"
|
526
647
|
closed!
|
@@ -562,6 +683,20 @@ module Bunny
|
|
562
683
|
end
|
563
684
|
end
|
564
685
|
|
686
|
+
def handle_ack_or_nack(delivery_tag, multiple, nack)
|
687
|
+
if multiple
|
688
|
+
@unconfirmed_set.delete_if { |i| i < delivery_tag }
|
689
|
+
else
|
690
|
+
@unconfirmed_set.delete(delivery_tag)
|
691
|
+
end
|
692
|
+
|
693
|
+
@unconfirmed_set_mutex.synchronize do
|
694
|
+
@only_acks_received = (@only_acks_received && !nack)
|
695
|
+
|
696
|
+
@confirms_continuations.push(true) if @unconfirmed_set.empty?
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
565
700
|
# Starts consumer work pool. Lazily called by #basic_consume to avoid creating new threads
|
566
701
|
# that won't do any real work for channels that do not register consumers (e.g. only used for
|
567
702
|
# publishing). MK.
|
@@ -576,7 +711,7 @@ module Bunny
|
|
576
711
|
# Synchronizes given block using this channel's mutex.
|
577
712
|
# @api public
|
578
713
|
def synchronize(&block)
|
579
|
-
@
|
714
|
+
@publishing_mutex.synchronize(&block)
|
580
715
|
end
|
581
716
|
|
582
717
|
def register_queue(queue)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "thread"
|
2
|
+
require "amq/int_allocator"
|
2
3
|
|
3
4
|
module Bunny
|
4
5
|
class ChannelIdAllocator
|
@@ -7,8 +8,7 @@ module Bunny
|
|
7
8
|
# API
|
8
9
|
#
|
9
10
|
|
10
|
-
def initialize
|
11
|
-
max_channel = (1 << 16) - 1
|
11
|
+
def initialize(max_channel = ((1 << 16) - 1))
|
12
12
|
@int_allocator ||= AMQ::IntAllocator.new(1, max_channel)
|
13
13
|
|
14
14
|
@channel_id_mutex ||= Mutex.new
|
@@ -43,7 +43,7 @@ module Bunny
|
|
43
43
|
def allocated_channel_id?(i)
|
44
44
|
@channel_id_mutex.synchronize do
|
45
45
|
@int_allocator.allocated?(i)
|
46
|
-
end
|
46
|
+
end
|
47
47
|
end
|
48
48
|
|
49
49
|
# Resets channel allocator. This method is thread safe.
|
data/lib/bunny/consumer.rb
CHANGED
@@ -5,16 +5,19 @@ module Bunny
|
|
5
5
|
# API
|
6
6
|
#
|
7
7
|
|
8
|
-
attr_reader
|
9
|
-
attr_reader
|
10
|
-
|
11
|
-
attr_reader
|
8
|
+
attr_reader :channel
|
9
|
+
attr_reader :queue
|
10
|
+
attr_accessor :consumer_tag
|
11
|
+
attr_reader :arguments
|
12
|
+
attr_reader :no_ack
|
13
|
+
attr_reader :exclusive
|
12
14
|
|
13
15
|
|
14
|
-
|
16
|
+
|
17
|
+
def initialize(channel, queue, consumer_tag = "", no_ack = false, exclusive = false, arguments = {})
|
15
18
|
@channel = channel || raise(ArgumentError, "channel is nil")
|
16
|
-
@queue = queue
|
17
|
-
@consumer_tag = consumer_tag
|
19
|
+
@queue = queue || raise(ArgumentError, "queue is nil")
|
20
|
+
@consumer_tag = consumer_tag
|
18
21
|
@exclusive = exclusive
|
19
22
|
@arguments = arguments
|
20
23
|
@no_ack = no_ack
|
@@ -31,11 +34,15 @@ module Bunny
|
|
31
34
|
end
|
32
35
|
alias handle_delivery call
|
33
36
|
|
34
|
-
def
|
37
|
+
def on_cancellation(&block)
|
35
38
|
@on_cancellation = block
|
36
39
|
self
|
37
40
|
end
|
38
41
|
|
42
|
+
def handle_cancellation(basic_cancel)
|
43
|
+
@on_cancellation.call(basic_cancel) if @on_cancellation
|
44
|
+
end
|
45
|
+
|
39
46
|
def queue_name
|
40
47
|
if @queue.respond_to?(:name)
|
41
48
|
@queue.name
|
data/lib/bunny/queue.rb
CHANGED
@@ -74,16 +74,38 @@ module Bunny
|
|
74
74
|
self
|
75
75
|
end
|
76
76
|
|
77
|
-
def subscribe(opts = {
|
78
|
-
|
79
|
-
|
80
|
-
|
77
|
+
def subscribe(opts = {
|
78
|
+
:consumer_tag => "",
|
79
|
+
:ack => false,
|
80
|
+
:exclusive => false,
|
81
|
+
:block => false,
|
82
|
+
:on_cancellation => nil
|
83
|
+
}, &block)
|
84
|
+
|
85
|
+
ctag = opts.fetch(:consumer_tag, "")
|
86
|
+
consumer = Consumer.new(@channel,
|
87
|
+
@name,
|
88
|
+
ctag,
|
89
|
+
opts[:no_ack],
|
90
|
+
opts[:exclusive],
|
91
|
+
opts[:arguments])
|
92
|
+
consumer.on_delivery(&block)
|
93
|
+
consumer.on_cancellation(&opts[:on_cancellation]) if opts[:on_cancellation]
|
94
|
+
|
95
|
+
@channel.basic_consume_with(consumer)
|
96
|
+
if opts[:block]
|
81
97
|
# joins current thread with the consumers pool, will block
|
82
98
|
# the current thread for as long as the consumer pool is active
|
83
99
|
@channel.work_pool.join
|
84
100
|
end
|
85
101
|
end
|
86
102
|
|
103
|
+
def subscribe_with(consumer, opts = {:block => false})
|
104
|
+
@channel.basic_consume_with(consumer)
|
105
|
+
|
106
|
+
@channel.work_pool.join if opts[:block]
|
107
|
+
end
|
108
|
+
|
87
109
|
def pop(opts = {:ack => false}, &block)
|
88
110
|
delivery_info, properties, content = @channel.basic_get(@name, opts)
|
89
111
|
|
data/lib/bunny/session.rb
CHANGED
@@ -30,11 +30,16 @@ module Bunny
|
|
30
30
|
|
31
31
|
DEFAULT_CLIENT_PROPERTIES = {
|
32
32
|
# once we support AMQP 0.9.1 extensions, this needs to be updated. MK.
|
33
|
-
:capabilities => {
|
33
|
+
:capabilities => {
|
34
|
+
# :publisher_confirms => true,
|
35
|
+
:consumer_cancel_notify => true,
|
36
|
+
:exchange_exchange_bindings => true,
|
37
|
+
:"basic.nack" => true
|
38
|
+
},
|
34
39
|
:product => "Bunny",
|
35
40
|
:platform => ::RUBY_DESCRIPTION,
|
36
41
|
:version => Bunny::VERSION,
|
37
|
-
:information => "http://github.com/ruby-amqp/bunny"
|
42
|
+
:information => "http://github.com/ruby-amqp/bunny",
|
38
43
|
}
|
39
44
|
|
40
45
|
DEFAULT_LOCALE = "en_GB"
|
@@ -50,7 +55,9 @@ module Bunny
|
|
50
55
|
attr_reader :channel_id_allocator
|
51
56
|
|
52
57
|
def initialize(connection_string_or_opts = Hash.new, optz = Hash.new)
|
53
|
-
opts = case connection_string_or_opts
|
58
|
+
opts = case (ENV["RABBITMQ_URL"] || connection_string_or_opts)
|
59
|
+
when nil then
|
60
|
+
Hash.new
|
54
61
|
when String then
|
55
62
|
AMQ::Settings.parse_amqp_url(connection_string_or_opts)
|
56
63
|
when Hash then
|
@@ -66,22 +73,18 @@ module Bunny
|
|
66
73
|
@logfile = opts[:logfile]
|
67
74
|
@logging = opts[:logging] || false
|
68
75
|
|
69
|
-
@status
|
70
|
-
|
71
|
-
#
|
72
|
-
@
|
73
|
-
@
|
76
|
+
@status = :not_connected
|
77
|
+
|
78
|
+
# these are negotiated with the broker during the connection tuning phase
|
79
|
+
@client_frame_max = opts.fetch(:frame_max, DEFAULT_FRAME_MAX)
|
80
|
+
@client_channel_max = opts.fetch(:channel_max, 65536)
|
81
|
+
@client_heartbeat = self.heartbeat_from(opts)
|
74
82
|
|
75
83
|
@client_properties = opts[:properties] || DEFAULT_CLIENT_PROPERTIES
|
76
84
|
@mechanism = "PLAIN"
|
77
85
|
@locale = @opts.fetch(:locale, DEFAULT_LOCALE)
|
78
|
-
|
79
|
-
@
|
80
|
-
@channel_mutex = Mutex.new
|
81
|
-
@channels = Hash.new
|
82
|
-
|
83
|
-
# Create channel 0
|
84
|
-
@channel0 = Bunny::Channel.new(self, 0)
|
86
|
+
@channel_mutex = Mutex.new
|
87
|
+
@channels = Hash.new
|
85
88
|
|
86
89
|
@continuations = ::Queue.new
|
87
90
|
end
|
@@ -101,10 +104,6 @@ module Bunny
|
|
101
104
|
end
|
102
105
|
alias ssl? uses_ssl?
|
103
106
|
|
104
|
-
def channel0
|
105
|
-
@channel0
|
106
|
-
end
|
107
|
-
|
108
107
|
def start
|
109
108
|
@status = :connecting
|
110
109
|
|
@@ -442,9 +441,13 @@ module Bunny
|
|
442
441
|
raise Bunny::PossibleAuthenticationFailureError.new(self.user, self.vhost, self.password.size)
|
443
442
|
end
|
444
443
|
|
445
|
-
connection_tune
|
446
|
-
|
447
|
-
@
|
444
|
+
connection_tune = frame.decode_payload
|
445
|
+
|
446
|
+
@frame_max = negotiate_value(@client_frame_max, connection_tune.frame_max)
|
447
|
+
@channel_max = negotiate_value(@client_channel_max, connection_tune.channel_max)
|
448
|
+
@heartbeat = negotiate_value(@client_heartbeat, connection_tune.heartbeat)
|
449
|
+
|
450
|
+
@channel_id_allocator = ChannelIdAllocator.new(@channel_max)
|
448
451
|
|
449
452
|
@transport.send_frame(AMQ::Protocol::Connection::TuneOk.encode(@channel_max, @frame_max, @heartbeat))
|
450
453
|
@transport.send_frame(AMQ::Protocol::Connection::Open.encode(self.vhost))
|
@@ -470,6 +473,14 @@ module Bunny
|
|
470
473
|
raise "could not open connection: server did not respond with connection.open-ok" unless connection_open_ok.is_a?(AMQ::Protocol::Connection::OpenOk)
|
471
474
|
end
|
472
475
|
|
476
|
+
def negotiate_value(client_value, server_value)
|
477
|
+
if client_value == 0 || server_value == 0
|
478
|
+
[client_value, server_value].max
|
479
|
+
else
|
480
|
+
[client_value, server_value].min
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
473
484
|
def initialize_heartbeat_sender
|
474
485
|
@heartbeat_sender = HeartbeatSender.new(@transport)
|
475
486
|
@heartbeat_sender.start(@heartbeat)
|
data/lib/bunny/version.rb
CHANGED
@@ -303,9 +303,15 @@ describe Bunny::Session do
|
|
303
303
|
props["product"].should_not be_nil
|
304
304
|
props["platform"].should_not be_nil
|
305
305
|
props["version"].should_not be_nil
|
306
|
+
props["capabilities"].should_not be_nil
|
306
307
|
end
|
307
308
|
|
308
309
|
it "uses provided heartbeat interval" do
|
310
|
+
subject.start
|
311
|
+
subject.should be_connected
|
312
|
+
|
313
|
+
# this is negotiated with RabbitMQ, so we need to
|
314
|
+
# establish the connection first
|
309
315
|
subject.heartbeat.should == interval
|
310
316
|
end
|
311
317
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Bunny::Channel do
|
4
|
+
let(:connection) do
|
5
|
+
c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
|
6
|
+
c.start
|
7
|
+
c
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
connection.close if connection.open?
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with implicit consumer construction" do
|
15
|
+
let(:queue_name) { "basic.consume#{rand}" }
|
16
|
+
|
17
|
+
it "supports consumer cancellation notifications" do
|
18
|
+
cancelled = false
|
19
|
+
|
20
|
+
ch = connection.create_channel
|
21
|
+
t = Thread.new do
|
22
|
+
ch2 = connection.create_channel
|
23
|
+
q = ch2.queue(queue_name, :auto_delete => true)
|
24
|
+
|
25
|
+
q.subscribe(:on_cancellation => Proc.new { cancelled = true })
|
26
|
+
end
|
27
|
+
t.abort_on_exception = true
|
28
|
+
|
29
|
+
sleep 0.5
|
30
|
+
x = ch.default_exchange
|
31
|
+
x.publish("abc", :routing_key => queue_name)
|
32
|
+
|
33
|
+
sleep 0.5
|
34
|
+
ch.queue(queue_name, :auto_delete => true).delete
|
35
|
+
|
36
|
+
sleep 0.5
|
37
|
+
cancelled.should be_true
|
38
|
+
|
39
|
+
ch.close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
context "with explicit consumer construction" do
|
45
|
+
class ExampleConsumer < Bunny::Consumer
|
46
|
+
def cancelled?
|
47
|
+
@cancelled
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_cancellation(_)
|
51
|
+
@cancelled = true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
let(:queue_name) { "basic.consume#{rand}" }
|
56
|
+
|
57
|
+
it "supports consumer cancellation notifications" do
|
58
|
+
consumer = nil
|
59
|
+
|
60
|
+
ch = connection.create_channel
|
61
|
+
t = Thread.new do
|
62
|
+
ch2 = connection.create_channel
|
63
|
+
q = ch2.queue(queue_name, :auto_delete => true)
|
64
|
+
|
65
|
+
consumer = ExampleConsumer.new(ch2, q)
|
66
|
+
q.subscribe_with(consumer)
|
67
|
+
end
|
68
|
+
t.abort_on_exception = true
|
69
|
+
|
70
|
+
sleep 0.5
|
71
|
+
x = ch.default_exchange
|
72
|
+
x.publish("abc", :routing_key => queue_name)
|
73
|
+
|
74
|
+
sleep 0.5
|
75
|
+
ch.queue(queue_name, :auto_delete => true).delete
|
76
|
+
|
77
|
+
sleep 0.5
|
78
|
+
consumer.should be_cancelled
|
79
|
+
|
80
|
+
ch.close
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Bunny::Channel do
|
4
|
+
let(:connection) do
|
5
|
+
c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
|
6
|
+
c.start
|
7
|
+
c
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
connection.close if connection.open?
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
context "when publishing with confirms enabled" do
|
16
|
+
it "increments delivery index" do
|
17
|
+
ch = connection.create_channel
|
18
|
+
ch.should_not be_using_publisher_confirmations
|
19
|
+
|
20
|
+
ch.confirm_select
|
21
|
+
ch.should be_using_publisher_confirmations
|
22
|
+
|
23
|
+
q = ch.queue("", :exclusive => true)
|
24
|
+
x = ch.default_exchange
|
25
|
+
|
26
|
+
5000.times do
|
27
|
+
x.publish("xyzzy", :routing_key => q.name)
|
28
|
+
end
|
29
|
+
|
30
|
+
ch.next_publish_seq_no.should == 5001
|
31
|
+
ch.wait_for_confirms
|
32
|
+
|
33
|
+
q.message_count.should == 5000
|
34
|
+
q.purge
|
35
|
+
|
36
|
+
ch.close
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: bunny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 6
|
5
|
-
version: 0.9.0.
|
5
|
+
version: 0.9.0.pre3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Chris Duncan
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-
|
17
|
+
date: 2012-12-01 00:00:00 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: amq-protocol
|
@@ -52,6 +52,7 @@ files:
|
|
52
52
|
- bin/ci/before_build.sh
|
53
53
|
- bunny.gemspec
|
54
54
|
- examples/connection/heartbeat.rb
|
55
|
+
- examples/connection/unknown_host.rb
|
55
56
|
- examples/guides/getting_started/blabbr.rb
|
56
57
|
- examples/guides/getting_started/hello_world.rb
|
57
58
|
- examples/guides/getting_started/weathr.rb
|
@@ -93,10 +94,12 @@ files:
|
|
93
94
|
- spec/higher_level_api/integration/channel_open_stress_spec.rb
|
94
95
|
- spec/higher_level_api/integration/confirm_select_spec.rb
|
95
96
|
- spec/higher_level_api/integration/connection_spec.rb
|
97
|
+
- spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb
|
96
98
|
- spec/higher_level_api/integration/exchange_bind_spec.rb
|
97
99
|
- spec/higher_level_api/integration/exchange_declare_spec.rb
|
98
100
|
- spec/higher_level_api/integration/exchange_delete_spec.rb
|
99
101
|
- spec/higher_level_api/integration/exchange_unbind_spec.rb
|
102
|
+
- spec/higher_level_api/integration/publisher_confirms_spec.rb
|
100
103
|
- spec/higher_level_api/integration/queue_bind_spec.rb
|
101
104
|
- spec/higher_level_api/integration/queue_declare_spec.rb
|
102
105
|
- spec/higher_level_api/integration/queue_delete_spec.rb
|
@@ -110,8 +113,8 @@ files:
|
|
110
113
|
- spec/unit/bunny_spec.rb
|
111
114
|
- spec/unit/concurrent/condition_spec.rb
|
112
115
|
homepage: http://github.com/ruby-amqp/bunny
|
113
|
-
licenses:
|
114
|
-
|
116
|
+
licenses:
|
117
|
+
- MIT
|
115
118
|
post_install_message:
|
116
119
|
rdoc_options: []
|
117
120
|
|
@@ -153,10 +156,12 @@ test_files:
|
|
153
156
|
- spec/higher_level_api/integration/channel_open_stress_spec.rb
|
154
157
|
- spec/higher_level_api/integration/confirm_select_spec.rb
|
155
158
|
- spec/higher_level_api/integration/connection_spec.rb
|
159
|
+
- spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb
|
156
160
|
- spec/higher_level_api/integration/exchange_bind_spec.rb
|
157
161
|
- spec/higher_level_api/integration/exchange_declare_spec.rb
|
158
162
|
- spec/higher_level_api/integration/exchange_delete_spec.rb
|
159
163
|
- spec/higher_level_api/integration/exchange_unbind_spec.rb
|
164
|
+
- spec/higher_level_api/integration/publisher_confirms_spec.rb
|
160
165
|
- spec/higher_level_api/integration/queue_bind_spec.rb
|
161
166
|
- spec/higher_level_api/integration/queue_declare_spec.rb
|
162
167
|
- spec/higher_level_api/integration/queue_delete_spec.rb
|