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 +4 -4
- data/.travis.yml +3 -3
- data/ChangeLog.md +24 -19
- data/README.md +16 -14
- data/benchmarks/synchronized_sorted_set.rb +53 -0
- data/lib/amq/protocol/extensions.rb +16 -0
- data/lib/bunny.rb +1 -0
- data/lib/bunny/channel.rb +38 -5
- data/lib/bunny/compatibility.rb +1 -0
- data/lib/bunny/concurrent/atomic_fixnum.rb +74 -0
- data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/bunny/consumer.rb +13 -0
- data/lib/bunny/delivery_info.rb +3 -1
- data/lib/bunny/version.rb +1 -1
- data/lib/bunny/versioned_delivery_tag.rb +28 -0
- data/spec/higher_level_api/integration/basic_ack_spec.rb +34 -19
- data/spec/higher_level_api/integration/basic_consume_spec.rb +62 -1
- data/spec/higher_level_api/integration/basic_get_spec.rb +1 -1
- data/spec/unit/concurrent/atomic_fixnum_spec.rb +35 -0
- data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +34 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a48be5011527d81ec653d8a687d5ece9ee4138f
|
4
|
+
data.tar.gz: ccf19ed7b3b9ec42e3e3654c8e5a6ee1cbe772eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bea1acb05abe8f527821d427c8a104d65c5d813996342068f00116a6832d9233d849233251c55e63b1f03b523a8b4d62fed5583cdc49cabff2d580759389c111
|
7
|
+
data.tar.gz: 51fc965da8fc766e8c55770d15df0d7494c68801001f5e2beb02956432919e7a151d1aa84b581f29c0b59d87d8b8415fe8d972e09ce18c90123f4823bbf96314
|
data/.travis.yml
CHANGED
@@ -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
|
-
|
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
|
data/ChangeLog.md
CHANGED
@@ -1,4 +1,26 @@
|
|
1
|
-
## Changes between Bunny 0.9.
|
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
|
65
|
-
|
66
|
-
for
|
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
|
-
|
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
|
+
[](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
|
85
|
+
gem install bunny
|
84
86
|
```
|
85
87
|
|
86
|
-
|
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.
|
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
|
data/lib/bunny.rb
CHANGED
data/lib/bunny/channel.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/bunny/compatibility.rb
CHANGED
@@ -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
|
data/lib/bunny/consumer.rb
CHANGED
@@ -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
|
#
|
data/lib/bunny/delivery_info.rb
CHANGED
@@ -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,
|
data/lib/bunny/version.rb
CHANGED
@@ -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 :
|
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
|
-
|
21
|
-
|
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
|
21
|
+
sleep 0.5
|
25
22
|
q.message_count.should == 1
|
26
23
|
delivery_details, properties, content = q.pop(:ack => true)
|
27
24
|
|
28
|
-
|
29
|
-
sleep(0.25)
|
25
|
+
ch.ack(delivery_details.delivery_tag, true)
|
30
26
|
q.message_count.should == 0
|
31
27
|
|
32
|
-
|
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
|
-
|
39
|
-
|
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
|
58
|
+
sleep 0.5
|
43
59
|
q.message_count.should == 1
|
44
60
|
_, _, content = q.pop(:ack => true)
|
45
61
|
|
46
|
-
|
62
|
+
ch.on_error do |ch, channel_close|
|
47
63
|
@channel_close = channel_close
|
48
64
|
end
|
49
|
-
|
50
|
-
|
51
|
-
sleep 0.5
|
65
|
+
ch.ack(82, true)
|
66
|
+
sleep 0.25
|
52
67
|
|
53
|
-
@channel_close.
|
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
|
|
@@ -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.
|
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-
|
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
|