bunny 0.9.0.pre2 → 0.9.0.pre3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|