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 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