bunny 1.0.0.pre2 → 1.0.0.pre3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog.md +6 -0
- 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 +124 -70
- 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/issues/issue141_spec.rb +44 -0
- data/spec/unit/concurrent/atomic_fixnum_spec.rb +35 -0
- data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +34 -0
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42830e201dcbd08b5367454288ce79c6924ea0d4
|
4
|
+
data.tar.gz: 04005efee192cb86d4e7af17776e53e13712525b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d1841d225856610228745312a60ae7e86cd175a40528902f2834aa1c51c548be68a74a481717127f8d4d15463670a4a7a80577983b7766ff841bcb51822a37c
|
7
|
+
data.tar.gz: 44dbd529f0b0d16ae0f3c115040a9b6879f85ef7e4da6668dbe869a10e5e2b752232828eec30b70292b1fd582e0876a8f9eeb420c81e6d05eb1ac22f9d1c8198
|
data/ChangeLog.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## Changes between Bunny 1.0.0.pre1 and 1.0.0.pre2
|
2
2
|
|
3
|
+
### Exclusivity Violation for Consumers Now Raises a Reasonable Exception
|
4
|
+
|
5
|
+
When a second consumer is registered for the same queue on different channels,
|
6
|
+
a reasonable exception (`Bunny::AccessRefused`) will be raised.
|
7
|
+
|
8
|
+
|
3
9
|
### Reentrant Mutex Implementation
|
4
10
|
|
5
11
|
Bunny now allows mutex impl to be configurable, uses reentrant Monitor
|
@@ -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
|
@@ -522,13 +533,13 @@ module Bunny
|
|
522
533
|
end
|
523
534
|
|
524
535
|
m = AMQ::Protocol::Basic::Publish.encode(@id,
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
536
|
+
payload,
|
537
|
+
meta,
|
538
|
+
exchange_name,
|
539
|
+
routing_key,
|
540
|
+
meta[:mandatory],
|
541
|
+
false,
|
542
|
+
@connection.frame_max)
|
532
543
|
@connection.send_frameset_without_timeout(m, self)
|
533
544
|
|
534
545
|
self
|
@@ -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!
|
@@ -765,9 +777,9 @@ module Bunny
|
|
765
777
|
def basic_nack(delivery_tag, multiple = false, requeue = false)
|
766
778
|
raise_if_no_longer_open!
|
767
779
|
@connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
|
768
|
-
|
769
|
-
|
770
|
-
|
780
|
+
delivery_tag,
|
781
|
+
multiple,
|
782
|
+
requeue))
|
771
783
|
|
772
784
|
nil
|
773
785
|
end
|
@@ -802,13 +814,13 @@ module Bunny
|
|
802
814
|
end
|
803
815
|
|
804
816
|
@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
817
|
+
queue_name,
|
818
|
+
consumer_tag,
|
819
|
+
false,
|
820
|
+
no_ack,
|
821
|
+
exclusive,
|
822
|
+
false,
|
823
|
+
arguments))
|
812
824
|
|
813
825
|
begin
|
814
826
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
@@ -822,6 +834,10 @@ module Bunny
|
|
822
834
|
raise e
|
823
835
|
end
|
824
836
|
|
837
|
+
# in case there is another exclusive consumer and we get a channel.close
|
838
|
+
# response here. MK.
|
839
|
+
raise_if_channel_close!(@last_basic_consume_ok)
|
840
|
+
|
825
841
|
# covers server-generated consumer tags
|
826
842
|
add_consumer(queue_name, @last_basic_consume_ok.consumer_tag, no_ack, exclusive, arguments, &block)
|
827
843
|
|
@@ -847,13 +863,13 @@ module Bunny
|
|
847
863
|
end
|
848
864
|
|
849
865
|
@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
866
|
+
consumer.queue_name,
|
867
|
+
consumer.consumer_tag,
|
868
|
+
false,
|
869
|
+
consumer.no_ack,
|
870
|
+
consumer.exclusive,
|
871
|
+
false,
|
872
|
+
consumer.arguments))
|
857
873
|
|
858
874
|
begin
|
859
875
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
@@ -867,6 +883,10 @@ module Bunny
|
|
867
883
|
raise e
|
868
884
|
end
|
869
885
|
|
886
|
+
# in case there is another exclusive consumer and we get a channel.close
|
887
|
+
# response here. MK.
|
888
|
+
raise_if_channel_close!(@last_basic_consume_ok)
|
889
|
+
|
870
890
|
# covers server-generated consumer tags
|
871
891
|
register_consumer(@last_basic_consume_ok.consumer_tag, consumer)
|
872
892
|
|
@@ -927,13 +947,13 @@ module Bunny
|
|
927
947
|
raise_if_no_longer_open!
|
928
948
|
|
929
949
|
@connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
950
|
+
name,
|
951
|
+
opts.fetch(:passive, false),
|
952
|
+
opts.fetch(:durable, false),
|
953
|
+
opts.fetch(:exclusive, false),
|
954
|
+
opts.fetch(:auto_delete, false),
|
955
|
+
false,
|
956
|
+
opts[:arguments]))
|
937
957
|
@last_queue_declare_ok = wait_on_continuations
|
938
958
|
|
939
959
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -956,10 +976,10 @@ module Bunny
|
|
956
976
|
raise_if_no_longer_open!
|
957
977
|
|
958
978
|
@connection.send_frame(AMQ::Protocol::Queue::Delete.encode(@id,
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
979
|
+
name,
|
980
|
+
opts[:if_unused],
|
981
|
+
opts[:if_empty],
|
982
|
+
false))
|
963
983
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
964
984
|
@last_queue_delete_ok = wait_on_continuations
|
965
985
|
end
|
@@ -1011,11 +1031,11 @@ module Bunny
|
|
1011
1031
|
end
|
1012
1032
|
|
1013
1033
|
@connection.send_frame(AMQ::Protocol::Queue::Bind.encode(@id,
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1034
|
+
name,
|
1035
|
+
exchange_name,
|
1036
|
+
opts[:routing_key],
|
1037
|
+
false,
|
1038
|
+
opts[:arguments]))
|
1019
1039
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
1020
1040
|
@last_queue_bind_ok = wait_on_continuations
|
1021
1041
|
end
|
@@ -1047,10 +1067,10 @@ module Bunny
|
|
1047
1067
|
end
|
1048
1068
|
|
1049
1069
|
@connection.send_frame(AMQ::Protocol::Queue::Unbind.encode(@id,
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1070
|
+
name,
|
1071
|
+
exchange_name,
|
1072
|
+
opts[:routing_key],
|
1073
|
+
opts[:arguments]))
|
1054
1074
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
1055
1075
|
@last_queue_unbind_ok = wait_on_continuations
|
1056
1076
|
end
|
@@ -1082,14 +1102,14 @@ module Bunny
|
|
1082
1102
|
raise_if_no_longer_open!
|
1083
1103
|
|
1084
1104
|
@connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1105
|
+
name,
|
1106
|
+
type.to_s,
|
1107
|
+
opts.fetch(:passive, false),
|
1108
|
+
opts.fetch(:durable, false),
|
1109
|
+
opts.fetch(:auto_delete, false),
|
1110
|
+
false,
|
1111
|
+
false,
|
1112
|
+
opts[:arguments]))
|
1093
1113
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
1094
1114
|
@last_exchange_declare_ok = wait_on_continuations
|
1095
1115
|
end
|
@@ -1112,9 +1132,9 @@ module Bunny
|
|
1112
1132
|
raise_if_no_longer_open!
|
1113
1133
|
|
1114
1134
|
@connection.send_frame(AMQ::Protocol::Exchange::Delete.encode(@id,
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1135
|
+
name,
|
1136
|
+
opts[:if_unused],
|
1137
|
+
false))
|
1118
1138
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
1119
1139
|
@last_exchange_delete_ok = wait_on_continuations
|
1120
1140
|
end
|
@@ -1154,11 +1174,11 @@ module Bunny
|
|
1154
1174
|
end
|
1155
1175
|
|
1156
1176
|
@connection.send_frame(AMQ::Protocol::Exchange::Bind.encode(@id,
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1177
|
+
destination_name,
|
1178
|
+
source_name,
|
1179
|
+
opts[:routing_key],
|
1180
|
+
false,
|
1181
|
+
opts[:arguments]))
|
1162
1182
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
1163
1183
|
@last_exchange_bind_ok = wait_on_continuations
|
1164
1184
|
end
|
@@ -1198,11 +1218,11 @@ module Bunny
|
|
1198
1218
|
end
|
1199
1219
|
|
1200
1220
|
@connection.send_frame(AMQ::Protocol::Exchange::Unbind.encode(@id,
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1221
|
+
destination_name,
|
1222
|
+
source_name,
|
1223
|
+
opts[:routing_key],
|
1224
|
+
false,
|
1225
|
+
opts[:arguments]))
|
1206
1226
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
1207
1227
|
@last_exchange_unbind_ok = wait_on_continuations
|
1208
1228
|
end
|
@@ -1399,6 +1419,7 @@ module Bunny
|
|
1399
1419
|
# this includes recovering bindings
|
1400
1420
|
recover_queues
|
1401
1421
|
recover_consumers
|
1422
|
+
increment_recoveries_counter
|
1402
1423
|
end
|
1403
1424
|
|
1404
1425
|
# Recovers basic.qos setting. Used by the Automatic Network Failure
|
@@ -1444,6 +1465,11 @@ module Bunny
|
|
1444
1465
|
end
|
1445
1466
|
end
|
1446
1467
|
|
1468
|
+
# @private
|
1469
|
+
def increment_recoveries_counter
|
1470
|
+
@recoveries_counter.increment
|
1471
|
+
end
|
1472
|
+
|
1447
1473
|
# @endgroup
|
1448
1474
|
|
1449
1475
|
|
@@ -1555,6 +1581,7 @@ module Bunny
|
|
1555
1581
|
|
1556
1582
|
# @private
|
1557
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)
|
1558
1585
|
@basic_get_continuations.push([basic_get_ok, properties, content])
|
1559
1586
|
end
|
1560
1587
|
|
@@ -1606,10 +1633,8 @@ module Bunny
|
|
1606
1633
|
|
1607
1634
|
@unconfirmed_set_mutex.synchronize do
|
1608
1635
|
@only_acks_received = (@only_acks_received && !nack)
|
1609
|
-
@logger.debug "Channel #{@id}: @only_acks_received = #{@only_acks_received.inspect}, nack: #{nack.inspect}"
|
1610
1636
|
|
1611
1637
|
@confirms_continuations.push(true) if @unconfirmed_set.empty?
|
1612
|
-
|
1613
1638
|
@confirms_callback.call(delivery_tag, multiple, nack) if @confirms_callback
|
1614
1639
|
end
|
1615
1640
|
end
|
@@ -1786,6 +1811,19 @@ module Bunny
|
|
1786
1811
|
raise ChannelAlreadyClosed.new("cannot use a channel that was already closed! Channel id: #{@id}", self) if closed?
|
1787
1812
|
end
|
1788
1813
|
|
1814
|
+
# @private
|
1815
|
+
def raise_if_channel_close!(method)
|
1816
|
+
if method && method.is_a?(AMQ::Protocol::Channel::Close)
|
1817
|
+
# basic.ack, basic.reject, basic.nack. MK.
|
1818
|
+
if channel_level_exception_after_operation_that_has_no_response?(method)
|
1819
|
+
@on_error.call(self, method) if @on_error
|
1820
|
+
else
|
1821
|
+
@last_channel_error = instantiate_channel_level_exception(method)
|
1822
|
+
raise @last_channel_error
|
1823
|
+
end
|
1824
|
+
end
|
1825
|
+
end
|
1826
|
+
|
1789
1827
|
# @private
|
1790
1828
|
def reset_continuations
|
1791
1829
|
@continuations = new_continuation
|
@@ -1805,5 +1843,21 @@ module Bunny
|
|
1805
1843
|
Concurrent::ContinuationQueue.new
|
1806
1844
|
end
|
1807
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
|
1808
1862
|
end # Channel
|
1809
1863
|
end # Bunny
|
@@ -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,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Registering 2nd exclusive consumer on queue" 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 :each do
|
11
|
+
connection.close if connection.open?
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
it "raises a meaningful exception" do
|
16
|
+
xs = []
|
17
|
+
|
18
|
+
ch1 = connection.create_channel
|
19
|
+
ch2 = connection.create_channel
|
20
|
+
q1 = ch1.queue("", :auto_delete => true)
|
21
|
+
q2 = ch2.queue(q1.name, :auto_delete => true, :passive => true)
|
22
|
+
|
23
|
+
c1 = q1.subscribe(:exclusive => true) do |_, _, payload|
|
24
|
+
xs << payload
|
25
|
+
end
|
26
|
+
sleep 0.1
|
27
|
+
|
28
|
+
lambda do
|
29
|
+
q2.subscribe(:exclusive => true) do |_, _, _|
|
30
|
+
end
|
31
|
+
end.should raise_error(Bunny::AccessRefused)
|
32
|
+
|
33
|
+
ch1.should be_open
|
34
|
+
ch2.should be_closed
|
35
|
+
|
36
|
+
q1.publish("abc")
|
37
|
+
sleep 0.1
|
38
|
+
|
39
|
+
# verify that the first consumer is fine
|
40
|
+
xs.should == ["abc"]
|
41
|
+
|
42
|
+
q1.delete
|
43
|
+
end
|
44
|
+
end
|
@@ -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: 1.0.0.
|
4
|
+
version: 1.0.0.pre3
|
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
|
@@ -162,6 +167,7 @@ files:
|
|
162
167
|
- spec/higher_level_api/integration/tx_commit_spec.rb
|
163
168
|
- spec/higher_level_api/integration/tx_rollback_spec.rb
|
164
169
|
- spec/issues/issue100_spec.rb
|
170
|
+
- spec/issues/issue141_spec.rb
|
165
171
|
- spec/issues/issue78_spec.rb
|
166
172
|
- spec/issues/issue83_spec.rb
|
167
173
|
- spec/issues/issue97_attachment.json
|
@@ -181,8 +187,10 @@ files:
|
|
181
187
|
- spec/tls/server_cert.pem
|
182
188
|
- spec/tls/server_key.pem
|
183
189
|
- spec/unit/bunny_spec.rb
|
190
|
+
- spec/unit/concurrent/atomic_fixnum_spec.rb
|
184
191
|
- spec/unit/concurrent/condition_spec.rb
|
185
192
|
- spec/unit/concurrent/linked_continuation_queue_spec.rb
|
193
|
+
- spec/unit/concurrent/synchronized_sorted_set_spec.rb
|
186
194
|
homepage: http://rubybunny.info
|
187
195
|
licenses:
|
188
196
|
- MIT
|
@@ -250,6 +258,7 @@ test_files:
|
|
250
258
|
- spec/higher_level_api/integration/tx_commit_spec.rb
|
251
259
|
- spec/higher_level_api/integration/tx_rollback_spec.rb
|
252
260
|
- spec/issues/issue100_spec.rb
|
261
|
+
- spec/issues/issue141_spec.rb
|
253
262
|
- spec/issues/issue78_spec.rb
|
254
263
|
- spec/issues/issue83_spec.rb
|
255
264
|
- spec/issues/issue97_attachment.json
|
@@ -269,6 +278,8 @@ test_files:
|
|
269
278
|
- spec/tls/server_cert.pem
|
270
279
|
- spec/tls/server_key.pem
|
271
280
|
- spec/unit/bunny_spec.rb
|
281
|
+
- spec/unit/concurrent/atomic_fixnum_spec.rb
|
272
282
|
- spec/unit/concurrent/condition_spec.rb
|
273
283
|
- spec/unit/concurrent/linked_continuation_queue_spec.rb
|
284
|
+
- spec/unit/concurrent/synchronized_sorted_set_spec.rb
|
274
285
|
has_rdoc: true
|