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 CHANGED
@@ -1,9 +1,8 @@
1
1
  --no-private
2
2
  --protected
3
- --markup="textile" lib/**/*.rb
4
- --main README.textile
5
- --asset examples:examples
3
+ --markup="markdown" lib/**/*.rb
4
+ --main README.md
6
5
  --hide-tag todo
7
6
  -
8
7
  LICENSE
9
- CHANGELOG
8
+ ChangeLog.md
@@ -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 – 2011 Chris Duncan, Jakub Stastny aka botanicus,
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
@@ -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 = [
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require 'bunny'
10
+
11
+ b = Bunny.new("amqp://guest:guest@aksjhdkajshdkj.example82737.com")
12
+ b.start
13
+
@@ -1,5 +1,5 @@
1
1
  require "thread"
2
- require "amq/int_allocator"
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
- @mutex = Mutex.new
37
- @consumer_mutex = Mutex.new
37
+ @publishing_mutex = Mutex.new
38
+ @consumer_mutex = Mutex.new
38
39
 
39
- @continuations = ::Queue.new
40
- end
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
- @connection.send_frameset(AMQ::Protocol::Basic::Publish.encode(@id, payload, meta, exchange_name, routing_key, meta[:mandatory], false, @connection.frame_max), self)
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, delivery_tag, requeue, multiple))
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, queue_name, consumer_tag, false, no_ack, exclusive, false, arguments))
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
- c = Consumer.new(self, queue, consumer_tag, no_ack, exclusive, arguments)
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, name, opts.fetch(:passive, false), opts.fetch(:durable, false), opts.fetch(:exclusive, false), opts.fetch(:auto_delete, false), false, opts[:arguments]))
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, name, opts[:if_unused], opts[:if_empty], false))
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, name, exchange_name, opts[:routing_key], false, opts[:arguments]))
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, name, exchange_name, opts[:routing_key], opts[:arguments]))
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, name, type.to_s, opts.fetch(:passive, false), opts.fetch(:durable, false), opts.fetch(:auto_delete, false), false, false, opts[:arguments]))
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, name, opts[:if_unused], false))
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
- source.name
372
- else
373
- source
374
- end
459
+ source.name
460
+ else
461
+ source
462
+ end
375
463
 
376
464
  destination_name = if destination.respond_to?(:name)
377
- destination.name
378
- else
379
- destination
380
- end
381
-
382
- @connection.send_frame(AMQ::Protocol::Exchange::Bind.encode(@id, destination_name, source_name, opts[:routing_key], false, opts[:arguments]))
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
- source.name
396
- else
397
- source
398
- end
488
+ source.name
489
+ else
490
+ source
491
+ end
399
492
 
400
493
  destination_name = if destination.respond_to?(:name)
401
- destination.name
402
- else
403
- destination
404
- end
405
-
406
- @connection.send_frame(AMQ::Protocol::Exchange::Unbind.encode(@id, destination_name, source_name, opts[:routing_key], false, opts[:arguments]))
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
- @mutex.synchronize(&block)
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.
@@ -5,16 +5,19 @@ module Bunny
5
5
  # API
6
6
  #
7
7
 
8
- attr_reader :channel
9
- attr_reader :queue
10
- attr_reader :consumer_tag
11
- attr_reader :arguments
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
- def initialize(channel, queue, consumer_tag, no_ack = false, exclusive = false, arguments = {})
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 || raise(ArgumentError, "queue is nil")
17
- @consumer_tag = consumer_tag || raise(ArgumentError, "consumer tag is nil")
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 handle_cancel(&block)
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
@@ -74,16 +74,38 @@ module Bunny
74
74
  self
75
75
  end
76
76
 
77
- def subscribe(opts = {:consumer_tag => "", :ack => false, :exclusive => false, :block => false}, &block)
78
- @channel.basic_consume(@name, opts.fetch(:consumer_tag, ""), !opts[:ack], opts[:exclusive], opts[:arguments], &block)
79
-
80
- if options[:block]
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
 
@@ -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 = :not_connected
70
- @frame_max = opts[:frame_max] || DEFAULT_FRAME_MAX
71
- # currently ignored
72
- @channel_max = opts[:channel_max] || 0
73
- @heartbeat = self.heartbeat_from(opts)
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
- @channel_id_allocator = ChannelIdAllocator.new
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 = frame.decode_payload
446
- @frame_max = connection_tune.frame_max.freeze
447
- @heartbeat ||= connection_tune.heartbeat
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)
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Bunny
4
- VERSION = "0.9.0.pre2"
4
+ VERSION = "0.9.0.pre3"
5
5
  end
@@ -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.pre2
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-11-30 00:00:00 Z
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