bunny 0.9.8 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de36b24ae58dc0288bf7f25d55c84d4fca21488b
4
- data.tar.gz: eab9a6c204683443887320e9f54a29cc46c9b795
3
+ metadata.gz: 0a48be5011527d81ec653d8a687d5ece9ee4138f
4
+ data.tar.gz: ccf19ed7b3b9ec42e3e3654c8e5a6ee1cbe772eb
5
5
  SHA512:
6
- metadata.gz: 2d4f79815730c98bc8df51ac8c5b0f14c1040d29d4ccf8b9dae8ded2bee0b43275364fdba63fbee6c4ef7f108ae2a6f80b269aa67b07021cedb90b5addfc0f24
7
- data.tar.gz: 3790273b80d26a6ef6c910c59dc8d38a15e2c7d8bba9f5840598e77215e923b0d2418972acbb79ed2cfb52a0c0191700738ced4dc2923047afca9c4b387e2510
6
+ metadata.gz: bea1acb05abe8f527821d427c8a104d65c5d813996342068f00116a6832d9233d849233251c55e63b1f03b523a8b4d62fed5583cdc49cabff2d580759389c111
7
+ data.tar.gz: 51fc965da8fc766e8c55770d15df0d7494c68801001f5e2beb02956432919e7a151d1aa84b581f29c0b59d87d8b8415fe8d972e09ce18c90123f4823bbf96314
@@ -4,13 +4,13 @@ script: "bundle exec rspec -c spec"
4
4
  rvm:
5
5
  - "2.0"
6
6
  - "1.9.3"
7
- - "rbx-19mode"
8
7
  - "jruby-19mode"
9
8
  - "1.9.2"
10
- services:
11
- - rabbitmq
9
+ - "rbx-19mode"
12
10
  notifications:
13
11
  email: michael@rabbitmq.com
12
+ services:
13
+ - rabbitmq
14
14
  branches:
15
15
  only:
16
16
  - master
@@ -1,4 +1,26 @@
1
- ## Changes between Bunny 0.9.7 and 0.9.8
1
+ ## Changes between Bunny 0.9.0 and 0.10.0
2
+
3
+ This release has one minor **breaking API change**.
4
+
5
+ ### Safe[r] basic.ack, basic.nack and basic.reject implementation
6
+
7
+ Previously if a channel was recovered (reopened) by automatic connection
8
+ recovery before a message was acknowledged or rejected, it would cause
9
+ any operation on the channel that uses delivery tags to fail and
10
+ cause the channel to be closed.
11
+
12
+ To avoid this issue, every channel keeps a counter of how many times
13
+ it has been reopened and marks delivery tags with them. Using a stale
14
+ tag to ack or reject a message will produce no method sent to RabbitMQ.
15
+ Note that unacknowledged messages will be requeued by RabbitMQ when connection
16
+ goes down anyway.
17
+
18
+ This involves an API change: `Bunny::DeliveryMetadata#delivery_tag` is now
19
+ and instance of a class that responds to `#tag` and `#to_i` and is accepted
20
+ by `Bunny::Channel#ack` and related methods.
21
+
22
+ Integers are still accepted by the same methods.
23
+
2
24
 
3
25
  ### Exclusivity Violation for Consumers Now Raises a Reasonable Exception
4
26
 
@@ -6,8 +28,6 @@ When a second consumer is registered for the same queue on different channels,
6
28
  a reasonable exception (`Bunny::AccessRefused`) will be raised.
7
29
 
8
30
 
9
- ## Changes between Bunny 0.9.6 and 0.9.7
10
-
11
31
  ### Reentrant Mutex Implementation
12
32
 
13
33
  Bunny now allows mutex impl to be configurable, uses reentrant Monitor
@@ -23,18 +43,12 @@ for Bunny.
23
43
  Apps that need these 4% can configure what mutex implementation
24
44
  is used on per-connection basis.
25
45
 
26
-
27
- ## Changes between Bunny 0.9.5 and 0.9.6
28
-
29
46
  ### Eliminated Race Condition in Bunny::Session#close
30
47
 
31
48
  `Bunny::Session#close` had a race condition that caused (non-deterministic)
32
49
  exceptions when connection transport was closed before connection
33
50
  reader loop was guaranteed to have stopped.
34
51
 
35
-
36
- ## Changes between Bunny 0.9.4 and 0.9.5
37
-
38
52
  ### connection.close Raises Exceptions on Connection Thread
39
53
 
40
54
  Connection-level exceptions (including when a connection is closed via
@@ -44,9 +58,6 @@ thread so they
44
58
  * can be handled by applications
45
59
  * do not start connection recovery, which may be uncalled for
46
60
 
47
-
48
- ## Changes between Bunny 0.9.3 and 0.9.4
49
-
50
61
  ### Client TLS Certificates are Optional
51
62
 
52
63
  Bunny will no longer require client TLS certificates. Note that CA certificate
@@ -56,8 +67,6 @@ If RabbitMQ TLS configuration requires peer verification, client certificate
56
67
  and private key are mandatory.
57
68
 
58
69
 
59
- ## Changes between Bunny 0.9.2 and 0.9.3
60
-
61
70
  ### Publishing Over Closed Connections
62
71
 
63
72
  Publishing a message over a closed connection (during a network outage, before the connection
@@ -66,17 +75,12 @@ is open) will now correctly result in an exception.
66
75
  Contributed by Matt Campbell.
67
76
 
68
77
 
69
- ## Changes between Bunny 0.9.1 and 0.9.2
70
-
71
78
  ### Reliability Improvement in Automatic Network Failure Recovery
72
79
 
73
80
  Bunny now ensures a new connection transport (socket) is initialized
74
81
  before any recovery is attempted.
75
82
 
76
83
 
77
-
78
- ## Changes between Bunny 0.9.0 and 0.9.1
79
-
80
84
  ### Reliability Improvement in Bunny::Session#create_channel
81
85
 
82
86
  `Bunny::Session#create_channel` now uses two separate mutexes to avoid
@@ -84,6 +88,7 @@ a (very rare) issue when the previous implementation would try to
84
88
  re-acquire the same mutex and fail (Ruby mutexes are non-reentrant).
85
89
 
86
90
 
91
+
87
92
  ## Changes between Bunny 0.9.0.rc1 and 0.9.0.rc2
88
93
 
89
94
  ### Channel Now Properly Restarts Consumer Pool
data/README.md CHANGED
@@ -61,34 +61,36 @@ Bunny `0.7.x` and earlier versions support RabbitMQ 1.x and 2.x.
61
61
 
62
62
  ## Project Maturity
63
63
 
64
- Bunny is a pretty old (started circa late 2008) library that, before version 0.9, **a lot** of missing functionality. Version 0.9
65
- can be considered to be "second birthday" for Bunny as it was rewritten from scratch. Key objectives
66
- for 0.9 are
64
+ Bunny is a pretty old (started circa late 2008) library that, before
65
+ version 0.9, **a lot** of missing functionality. Version 0.9 can be
66
+ considered to be "second birthday" for Bunny as it was rewritten from
67
+ scratch with over a dozen of preview releases over the course of about
68
+ a year.
67
69
 
68
- * Be feature complete, support all RabbitMQ 3.x features
69
- * Eliminate limitations Bunny used to have with earlier versions
70
- * Be [well documented](http://rubybunny.info)
71
- * Make use of concurrency and, if the runtime provides it, parallelism
72
- * Reuse code with amqp gem and possibly other clients where it makes sense
73
-
74
- We (the maintainers) make our best to keep the new version as
70
+ We (the maintainers) made our best effort to keep the new version as
75
71
  backwards compatible as possible but within reason.
76
72
 
77
73
 
78
74
  ## Installation & Bundler Dependency
79
75
 
76
+ ### Most Recent Release
77
+
78
+ [![Gem Version](https://badge.fury.io/rb/bunny.png)](http://badge.fury.io/rb/bunny)
79
+
80
+ ### With Rubygems
81
+
80
82
  To install Bunny 0.9.x with RubyGems:
81
83
 
82
84
  ```
83
- gem install bunny --version ">= 0.9.2"
85
+ gem install bunny
84
86
  ```
85
87
 
86
- the most recent 0.9.x version is `0.9.2`.
88
+ ### Bundler Dependency
87
89
 
88
90
  To use Bunny 0.9.x in a project managed with Bundler:
89
91
 
90
92
  ``` ruby
91
- gem "bunny", ">= 0.9.2"
93
+ gem "bunny", ">= 0.9.3"
92
94
  ```
93
95
 
94
96
 
@@ -154,7 +156,7 @@ Other documentation guides are available at [rubybunny.info](http://rubybunny.in
154
156
 
155
157
  ### Mailing List
156
158
 
157
- [Bunny a mailing list](http://groups.google.com/group/ruby-amqp). We encourage you
159
+ [Bunny has a mailing list](http://groups.google.com/group/ruby-amqp). We encourage you
158
160
  to also join the [rabbitmq-discuss](https://lists.rabbitmq.com/cgi-bin/mailman/listinfo/rabbitmq-discuss) mailing list. Feel free to ask any questions that you may have.
159
161
 
160
162
 
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "rubygems"
5
+ require "set"
6
+ require "thread"
7
+ require "benchmark"
8
+
9
+ require "bunny/concurrent/synchronized_sorted_set"
10
+
11
+ puts
12
+ puts "-" * 80
13
+ puts "Benchmarking on #{RUBY_DESCRIPTION}"
14
+
15
+ n = 2_000_000
16
+ s = SortedSet.new
17
+
18
+ # warm up the JIT, etc
19
+ puts "Doing a warmup run..."
20
+ n.times do |i|
21
+ s << 1
22
+ s << i
23
+ s.delete i
24
+ s << i
25
+ end
26
+
27
+ t1 = Benchmark.realtime do
28
+ n.times do |i|
29
+ s << 1
30
+ s << i
31
+ s.delete i
32
+ s << i
33
+ s.length
34
+ end
35
+ end
36
+ r1 = (n.to_f/t1.to_f)
37
+
38
+ s2 = SynchronizedSortedSet.new
39
+ t2 = Benchmark.realtime do
40
+ n.times do |i|
41
+ s2 << 1
42
+ s2 << i
43
+ s2.delete i
44
+ s2 << i
45
+ s2.length
46
+ end
47
+ end
48
+ r2 = (n.to_f/t2.to_f)
49
+
50
+ puts "Mixed sorted set ops, rate: #{(r1 / 1000).round(2)} KGHz"
51
+ puts "Mixed synchronized sorted set ops, rate: #{(r2 / 1000).round(2)} KGHz"
52
+ puts
53
+ puts "-" * 80
@@ -0,0 +1,16 @@
1
+ # @private
2
+ module AMQ
3
+ # @private
4
+ module Protocol
5
+ # @private
6
+ class Basic
7
+ # Extended to allow wrapping delivery tag into
8
+ # a versioned one.
9
+ #
10
+ # @private
11
+ class GetOk
12
+ attr_writer :delivery_tag
13
+ end
14
+ end
15
+ end
16
+ end
@@ -4,6 +4,7 @@ require "timeout"
4
4
 
5
5
  require "bunny/version"
6
6
  require "amq/protocol/client"
7
+ require "amq/protocol/extensions"
7
8
 
8
9
  require "bunny/framing"
9
10
  require "bunny/exceptions"
@@ -3,6 +3,7 @@ require "thread"
3
3
  require "monitor"
4
4
  require "set"
5
5
 
6
+ require "bunny/concurrent/atomic_fixnum"
6
7
  require "bunny/consumer_work_pool"
7
8
 
8
9
  require "bunny/exchange"
@@ -189,8 +190,12 @@ module Bunny
189
190
  @threads_waiting_on_basic_get_continuations = Set.new
190
191
 
191
192
  @next_publish_seq_no = 0
193
+
194
+ @recoveries_counter = Bunny::Concurrent::AtomicFixnum.new(0)
192
195
  end
193
196
 
197
+ attr_reader :recoveries_counter
198
+
194
199
  # @private
195
200
  def read_write_timeout
196
201
  @connection.read_write_timeout
@@ -438,7 +443,9 @@ module Bunny
438
443
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
439
444
  # @api public
440
445
  def reject(delivery_tag, requeue = false)
441
- basic_reject(delivery_tag, requeue)
446
+ guarding_against_stale_delivery_tags(delivery_tag) do
447
+ basic_reject(delivery_tag.to_i, requeue)
448
+ end
442
449
  end
443
450
 
444
451
  # Acknowledges a message. Acknowledged messages are completely removed from the queue.
@@ -449,7 +456,9 @@ module Bunny
449
456
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
450
457
  # @api public
451
458
  def ack(delivery_tag, multiple = false)
452
- basic_ack(delivery_tag, multiple)
459
+ guarding_against_stale_delivery_tags(delivery_tag) do
460
+ basic_ack(delivery_tag.to_i, multiple)
461
+ end
453
462
  end
454
463
  alias acknowledge ack
455
464
 
@@ -464,7 +473,9 @@ module Bunny
464
473
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
465
474
  # @api public
466
475
  def nack(delivery_tag, multiple = false, requeue = false)
467
- basic_nack(delivery_tag, multiple, requeue)
476
+ guarding_against_stale_delivery_tags(delivery_tag) do
477
+ basic_nack(delivery_tag.to_i, multiple, requeue)
478
+ end
468
479
  end
469
480
 
470
481
  # @endgroup
@@ -701,6 +712,7 @@ module Bunny
701
712
  # ch.basic_ack(delivery_info.delivery_tag, true)
702
713
  #
703
714
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
715
+ # @see #basic_ack_known_delivery_tag
704
716
  # @api public
705
717
  def basic_ack(delivery_tag, multiple)
706
718
  raise_if_no_longer_open!
@@ -1407,6 +1419,7 @@ module Bunny
1407
1419
  # this includes recovering bindings
1408
1420
  recover_queues
1409
1421
  recover_consumers
1422
+ increment_recoveries_counter
1410
1423
  end
1411
1424
 
1412
1425
  # Recovers basic.qos setting. Used by the Automatic Network Failure
@@ -1452,6 +1465,11 @@ module Bunny
1452
1465
  end
1453
1466
  end
1454
1467
 
1468
+ # @private
1469
+ def increment_recoveries_counter
1470
+ @recoveries_counter.increment
1471
+ end
1472
+
1455
1473
  # @endgroup
1456
1474
 
1457
1475
 
@@ -1563,6 +1581,7 @@ module Bunny
1563
1581
 
1564
1582
  # @private
1565
1583
  def handle_basic_get_ok(basic_get_ok, properties, content)
1584
+ basic_get_ok.delivery_tag = VersionedDeliveryTag.new(basic_get_ok.delivery_tag, @recoveries_counter.get)
1566
1585
  @basic_get_continuations.push([basic_get_ok, properties, content])
1567
1586
  end
1568
1587
 
@@ -1614,10 +1633,8 @@ module Bunny
1614
1633
 
1615
1634
  @unconfirmed_set_mutex.synchronize do
1616
1635
  @only_acks_received = (@only_acks_received && !nack)
1617
- @logger.debug "Channel #{@id}: @only_acks_received = #{@only_acks_received.inspect}, nack: #{nack.inspect}"
1618
1636
 
1619
1637
  @confirms_continuations.push(true) if @unconfirmed_set.empty?
1620
-
1621
1638
  @confirms_callback.call(delivery_tag, multiple, nack) if @confirms_callback
1622
1639
  end
1623
1640
  end
@@ -1826,5 +1843,21 @@ module Bunny
1826
1843
  Concurrent::ContinuationQueue.new
1827
1844
  end
1828
1845
  end # if defined?
1846
+
1847
+ # @private
1848
+ def guarding_against_stale_delivery_tags(tag, &block)
1849
+ case tag
1850
+ # if a fixnum was passed, execute unconditionally. MK.
1851
+ when Fixnum then
1852
+ block.call
1853
+ # versioned delivery tags should be checked to avoid
1854
+ # sending out stale (invalid) tags after channel was reopened
1855
+ # during network failure recovery. MK.
1856
+ when VersionedDeliveryTag then
1857
+ if !tag.stale?(@recoveries_counter.get)
1858
+ block.call
1859
+ end
1860
+ end
1861
+ end
1829
1862
  end # Channel
1830
1863
  end # Bunny
@@ -10,6 +10,7 @@ module Bunny
10
10
  #
11
11
 
12
12
  # @api public
13
+ # @private
13
14
  def channel_from(channel_or_connection)
14
15
  # Bunny 0.8.x and earlier completely hide channels from the API. So, queues and exchanges are
15
16
  # instantiated with a "Bunny object", which is a session. This function coerces two types of input to a
@@ -0,0 +1,74 @@
1
+ require "set"
2
+ require "thread"
3
+ require "monitor"
4
+
5
+ module Bunny
6
+ module Concurrent
7
+ # Minimalistic implementation of a synchronized fixnum value,
8
+ # designed after (but not implementing the entire API of!)
9
+ #
10
+ # @note Designed to be intentionally minimalistic and only cover Bunny's needs.
11
+ #
12
+ # @api public
13
+ class AtomicFixnum
14
+ def initialize(n = 0)
15
+ @n = n
16
+ @mutex = Monitor.new
17
+ end
18
+
19
+ def get
20
+ @mutex.synchronize do
21
+ @n
22
+ end
23
+ end
24
+
25
+ def set(n)
26
+ @mutex.synchronize do
27
+ @n = n
28
+ end
29
+ end
30
+
31
+ def increment
32
+ @mutex.synchronize do
33
+ @n = @n + 1
34
+ end
35
+ end
36
+ alias inc increment
37
+ alias increment_and_get increment
38
+
39
+ def get_and_add(i)
40
+ @mutex.synchronize do
41
+ v = @n
42
+ @n = @n + i
43
+
44
+ v
45
+ end
46
+ end
47
+
48
+ def get_and_increment
49
+ @mutex.synchronize do
50
+ v = @n
51
+ @n = @n + 1
52
+
53
+ v
54
+ end
55
+ end
56
+
57
+ def decrement
58
+ @mutex.synchronize do
59
+ @n = @n - 1
60
+ end
61
+ end
62
+ alias dec decrement
63
+ alias decrement_and_get decrement
64
+
65
+ def ==(m)
66
+ @mutex.synchronize { @n == m }
67
+ end
68
+
69
+ def ===(v)
70
+ @mutex.synchronize { @n === v }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,56 @@
1
+ require "set"
2
+ require "thread"
3
+
4
+ module Bunny
5
+ module Concurrent
6
+ # A SortedSet variation that synchronizes key mutation operations.
7
+ #
8
+ # @note This is NOT a complete SortedSet replacement. It only synchronizes operations needed by Bunny.
9
+ # @api public
10
+ class SynchronizedSortedSet < SortedSet
11
+ def initialize(enum = nil)
12
+ @mutex = Mutex.new
13
+
14
+ super
15
+ end
16
+
17
+ def add(o)
18
+ # avoid using Mutex#synchronize because of a Ruby 1.8.7-specific
19
+ # bug that prevents super from being called from within a block. MK.
20
+ @mutex.lock
21
+ begin
22
+ super
23
+ ensure
24
+ @mutex.unlock
25
+ end
26
+ end
27
+
28
+ def delete(o)
29
+ @mutex.lock
30
+ begin
31
+ super
32
+ ensure
33
+ @mutex.unlock
34
+ end
35
+ end
36
+
37
+ def delete_if(&block)
38
+ @mutex.lock
39
+ begin
40
+ super
41
+ ensure
42
+ @mutex.unlock
43
+ end
44
+ end
45
+
46
+ def include?(o)
47
+ @mutex.lock
48
+ begin
49
+ super
50
+ ensure
51
+ @mutex.unlock
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -37,6 +37,7 @@ module Bunny
37
37
  @consumer_tag = consumer_tag
38
38
  @exclusive = exclusive
39
39
  @arguments = arguments
40
+ # no_ack set to true = no manual ack = automatic ack. MK.
40
41
  @no_ack = no_ack
41
42
  end
42
43
 
@@ -89,6 +90,18 @@ module Bunny
89
90
  "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag}>"
90
91
  end
91
92
 
93
+ # @return [Boolean] true if this consumer uses automatic acknowledgement mode
94
+ # @api public
95
+ def automatic_acknowledgement?
96
+ @no_ack == false
97
+ end
98
+
99
+ # @return [Boolean] true if this consumer uses manual (explicit) acknowledgement mode
100
+ # @api public
101
+ def manual_acknowledgement?
102
+ @no_ack == true
103
+ end
104
+
92
105
  #
93
106
  # Recovery
94
107
  #
@@ -1,3 +1,5 @@
1
+ require "bunny/versioned_delivery_tag"
2
+
1
3
  module Bunny
2
4
  # Wraps {AMQ::Protocol::Basic::Deliver} to
3
5
  # provide access to the delivery properties as immutable hash as
@@ -24,7 +26,7 @@ module Bunny
24
26
  @basic_deliver = basic_deliver
25
27
  @hash = {
26
28
  :consumer_tag => basic_deliver.consumer_tag,
27
- :delivery_tag => basic_deliver.delivery_tag,
29
+ :delivery_tag => VersionedDeliveryTag.new(basic_deliver.delivery_tag, channel.recoveries_counter),
28
30
  :redelivered => basic_deliver.redelivered,
29
31
  :exchange => basic_deliver.exchange,
30
32
  :routing_key => basic_deliver.routing_key,
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "0.9.8"
5
+ VERSION = "0.10.0"
6
6
  end
@@ -0,0 +1,28 @@
1
+ module Bunny
2
+ # Wraps a delivery tag (which is an integer) so that {Bunny::Channel} could
3
+ # detect stale tags after connection recovery.
4
+ #
5
+ # @private
6
+ class VersionedDeliveryTag
7
+ attr_reader :tag
8
+ attr_reader :version
9
+
10
+ def initialize(tag, version)
11
+ raise ArgumentError.new("tag cannot be nil") unless tag
12
+ raise ArgumentError.new("version cannot be nil") unless version
13
+
14
+ @tag = tag
15
+ @version = version
16
+ end
17
+
18
+ def to_i
19
+ @tag
20
+ end
21
+
22
+ def stale?(version)
23
+ raise ArgumentError.new("version cannot be nil") unless version
24
+
25
+ @version < version
26
+ end
27
+ end
28
+ end
@@ -7,50 +7,65 @@ describe Bunny::Channel, "#ack" do
7
7
  c
8
8
  end
9
9
 
10
- after :all do
10
+ after :each do
11
11
  connection.close if connection.open?
12
12
  end
13
13
 
14
- subject do
15
- connection.create_channel
16
- end
17
-
18
14
  context "with a valid (known) delivery tag" do
19
15
  it "acknowleges a message" do
20
- q = subject.queue("bunny.basic.ack.manual-acks", :exclusive => true)
21
- x = subject.default_exchange
16
+ ch = connection.create_channel
17
+ q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
18
+ x = ch.default_exchange
22
19
 
23
20
  x.publish("bunneth", :routing_key => q.name)
24
- sleep(0.25)
21
+ sleep 0.5
25
22
  q.message_count.should == 1
26
23
  delivery_details, properties, content = q.pop(:ack => true)
27
24
 
28
- subject.ack(delivery_details.delivery_tag, true)
29
- sleep(0.25)
25
+ ch.ack(delivery_details.delivery_tag, true)
30
26
  q.message_count.should == 0
31
27
 
32
- subject.close
28
+ ch.close
29
+ end
30
+ end
31
+
32
+
33
+ context "with a valid (known) delivery tag and automatic ack mode" do
34
+ it "results in a channel exception" do
35
+ ch = connection.create_channel
36
+ q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
37
+ x = ch.default_exchange
38
+
39
+ q.subscribe(:manual_ack => false) do |delivery_info, properties, payload|
40
+ ch.ack(delivery_info.delivery_tag, false)
41
+ end
42
+
43
+ x.publish("bunneth", :routing_key => q.name)
44
+ sleep 0.5
45
+ lambda do
46
+ q.message_count
47
+ end.should raise_error(Bunny::ChannelAlreadyClosed)
33
48
  end
34
49
  end
35
50
 
36
51
  context "with an invalid (random) delivery tag" do
37
52
  it "causes a channel-level error" do
38
- q = subject.queue("bunny.basic.ack.unknown-delivery-tag", :exclusive => true)
39
- x = subject.default_exchange
53
+ ch = connection.create_channel
54
+ q = ch.queue("bunny.basic.ack.unknown-delivery-tag", :exclusive => true)
55
+ x = ch.default_exchange
40
56
 
41
57
  x.publish("bunneth", :routing_key => q.name)
42
- sleep(0.25)
58
+ sleep 0.5
43
59
  q.message_count.should == 1
44
60
  _, _, content = q.pop(:ack => true)
45
61
 
46
- subject.on_error do |ch, channel_close|
62
+ ch.on_error do |ch, channel_close|
47
63
  @channel_close = channel_close
48
64
  end
49
- subject.ack(82, true)
50
-
51
- sleep 0.5
65
+ ch.ack(82, true)
66
+ sleep 0.25
52
67
 
53
- @channel_close.reply_text.should == "PRECONDITION_FAILED - unknown delivery tag 82"
68
+ @channel_close.reply_code.should == AMQ::Protocol::PreconditionFailed::VALUE
54
69
  end
55
70
  end
56
71
  end
@@ -1,4 +1,5 @@
1
1
  require "spec_helper"
2
+ require "set"
2
3
 
3
4
  describe Bunny::Queue, "#subscribe" do
4
5
  let(:connection) do
@@ -41,6 +42,66 @@ describe Bunny::Queue, "#subscribe" do
41
42
 
42
43
  ch.close
43
44
  end
45
+
46
+ context "with a single consumer" do
47
+ let(:queue_name) { "bunny.basic_consume#{rand}" }
48
+
49
+ it "provides delivery tag access" do
50
+ delivery_tags = SortedSet.new
51
+
52
+ cch = connection.create_channel
53
+ q = cch.queue(queue_name, :auto_delete => true, :durable => false)
54
+ q.subscribe(:exclusive => false, :manual_ack => false) do |delivery_info, properties, payload|
55
+ delivery_tags << delivery_info.delivery_tag
56
+ end
57
+ sleep 0.5
58
+
59
+ ch = connection.create_channel
60
+ x = ch.default_exchange
61
+ 100.times do
62
+ x.publish("hello", :routing_key => queue_name)
63
+ end
64
+
65
+ sleep 1.0
66
+ delivery_tags.should == SortedSet.new(Range.new(1, 100).to_a)
67
+
68
+ ch.queue(queue_name, :auto_delete => true, :durable => false).message_count.should == 0
69
+
70
+ ch.close
71
+ end
72
+ end
73
+
74
+
75
+ context "with multiple consumers on the same channel" do
76
+ let(:queue_name) { "bunny.basic_consume#{rand}" }
77
+
78
+ it "provides delivery tag access" do
79
+ delivery_tags = SortedSet.new
80
+
81
+ cch = connection.create_channel
82
+ q = cch.queue(queue_name, :auto_delete => true, :durable => false)
83
+
84
+ 7.times do
85
+ q.subscribe(:exclusive => false, :manual_ack => false) do |delivery_info, properties, payload|
86
+ delivery_tags << delivery_info.delivery_tag
87
+ end
88
+ end
89
+ sleep 1.0
90
+
91
+ ch = connection.create_channel
92
+ x = ch.default_exchange
93
+ 100.times do
94
+ x.publish("hello", :routing_key => queue_name)
95
+ end
96
+
97
+ sleep 1.5
98
+ delivery_tags.should == SortedSet.new(Range.new(1, 100).to_a)
99
+
100
+ ch.queue(queue_name, :auto_delete => true, :durable => false).message_count.should == 0
101
+
102
+ ch.close
103
+ end
104
+ end
44
105
  end
45
106
 
46
107
  context "with manual acknowledgement mode" do
@@ -77,7 +138,7 @@ describe Bunny::Queue, "#subscribe" do
77
138
  end
78
139
  end
79
140
 
80
- 20.times do |i|
141
+ ENV.fetch("RUNS", 20).to_i.times do |i|
81
142
  context "with a queue that already has messages (take #{i})" do
82
143
  let(:queue_name) { "bunny.basic_consume#{rand}" }
83
144
 
@@ -8,7 +8,7 @@ describe Bunny::Queue, "#pop" do
8
8
  c
9
9
  end
10
10
 
11
- after :all do
11
+ after :each do
12
12
  connection.close if connection.open?
13
13
  end
14
14
 
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+ require "bunny/concurrent/atomic_fixnum"
3
+
4
+ describe Bunny::Concurrent::AtomicFixnum do
5
+ it "allows retrieving the current value" do
6
+ af = described_class.new(0)
7
+
8
+ af.get.should == 0
9
+ af.should == 0
10
+ end
11
+
12
+ it "can be updated" do
13
+ af = described_class.new(0)
14
+
15
+ af.get.should == 0
16
+ Thread.new do
17
+ af.set(10)
18
+ end
19
+ sleep 0.6
20
+ af.get.should == 10
21
+ end
22
+
23
+ it "can be incremented" do
24
+ af = described_class.new(0)
25
+
26
+ af.get.should == 0
27
+ 10.times do
28
+ Thread.new do
29
+ af.increment
30
+ end
31
+ end
32
+ sleep 0.6
33
+ af.get.should == 10
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+ require "bunny/concurrent/synchronized_sorted_set"
3
+
4
+ describe Bunny::Concurrent::SynchronizedSortedSet do
5
+ it "synchronizes common operations needed by Bunny" do
6
+ s = described_class.new
7
+ s.length.should == 0
8
+
9
+ 10.times do
10
+ Thread.new do
11
+ s << 1
12
+ s << 1
13
+ s << 2
14
+ s << 3
15
+ s << 4
16
+ s << 4
17
+ s << 4
18
+ s << 4
19
+ s << 5
20
+ s << 5
21
+ s << 5
22
+ s << 5
23
+ s << 6
24
+ s << 7
25
+ s << 8
26
+ s.delete 8
27
+ s.delete_if { |i| i == 1 }
28
+ end
29
+ end
30
+ sleep 2.0
31
+
32
+ s.length.should == 6
33
+ end
34
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.8
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2013-07-29 00:00:00.000000000 Z
15
+ date: 2013-08-03 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol
@@ -57,6 +57,7 @@ files:
57
57
  - benchmarks/queue_declare.rb
58
58
  - benchmarks/queue_declare_and_bind.rb
59
59
  - benchmarks/queue_declare_bind_and_delete.rb
60
+ - benchmarks/synchronized_sorted_set.rb
60
61
  - benchmarks/write_vs_write_nonblock.rb
61
62
  - bin/ci/before_build.sh
62
63
  - bunny.gemspec
@@ -90,6 +91,7 @@ files:
90
91
  - examples/guides/getting_started/weathr.rb
91
92
  - examples/guides/queues/one_off_consumer.rb
92
93
  - examples/guides/queues/redeliveries.rb
94
+ - lib/amq/protocol/extensions.rb
93
95
  - lib/bunny.rb
94
96
  - lib/bunny/authentication/credentials_encoder.rb
95
97
  - lib/bunny/authentication/external_mechanism_encoder.rb
@@ -97,9 +99,11 @@ files:
97
99
  - lib/bunny/channel.rb
98
100
  - lib/bunny/channel_id_allocator.rb
99
101
  - lib/bunny/compatibility.rb
102
+ - lib/bunny/concurrent/atomic_fixnum.rb
100
103
  - lib/bunny/concurrent/condition.rb
101
104
  - lib/bunny/concurrent/continuation_queue.rb
102
105
  - lib/bunny/concurrent/linked_continuation_queue.rb
106
+ - lib/bunny/concurrent/synchronized_sorted_set.rb
103
107
  - lib/bunny/consumer.rb
104
108
  - lib/bunny/consumer_tag_generator.rb
105
109
  - lib/bunny/consumer_work_pool.rb
@@ -119,6 +123,7 @@ files:
119
123
  - lib/bunny/test_kit.rb
120
124
  - lib/bunny/transport.rb
121
125
  - lib/bunny/version.rb
126
+ - lib/bunny/versioned_delivery_tag.rb
122
127
  - profiling/basic_publish/with_4K_messages.rb
123
128
  - spec/compatibility/queue_declare_spec.rb
124
129
  - spec/compatibility/queue_declare_with_default_channel_spec.rb
@@ -182,8 +187,10 @@ files:
182
187
  - spec/tls/server_cert.pem
183
188
  - spec/tls/server_key.pem
184
189
  - spec/unit/bunny_spec.rb
190
+ - spec/unit/concurrent/atomic_fixnum_spec.rb
185
191
  - spec/unit/concurrent/condition_spec.rb
186
192
  - spec/unit/concurrent/linked_continuation_queue_spec.rb
193
+ - spec/unit/concurrent/synchronized_sorted_set_spec.rb
187
194
  homepage: http://rubybunny.info
188
195
  licenses:
189
196
  - MIT
@@ -271,6 +278,8 @@ test_files:
271
278
  - spec/tls/server_cert.pem
272
279
  - spec/tls/server_key.pem
273
280
  - spec/unit/bunny_spec.rb
281
+ - spec/unit/concurrent/atomic_fixnum_spec.rb
274
282
  - spec/unit/concurrent/condition_spec.rb
275
283
  - spec/unit/concurrent/linked_continuation_queue_spec.rb
284
+ - spec/unit/concurrent/synchronized_sorted_set_spec.rb
276
285
  has_rdoc: true