bunny 0.9.8 → 0.10.0

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