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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b675d7cc26c0951af3fda991f438c8bbc3cb03c5
4
- data.tar.gz: 0cdf2879b75d769676c3dce890bd1d7213a96448
3
+ metadata.gz: 42830e201dcbd08b5367454288ce79c6924ea0d4
4
+ data.tar.gz: 04005efee192cb86d4e7af17776e53e13712525b
5
5
  SHA512:
6
- metadata.gz: e881cb1d9695f16102ca8a08bdd9191a38cc044c16e4c1f89a32b2331d0abd6d58a00abc731e3c809d9cd538a1d243eca9029074da228ee28400c2307e7deb4f
7
- data.tar.gz: 8390a12a2595bd1f7b65dd83c31686117081aa0672c258935e07c040ccf89222a1160e29cab0d519c24483f13adc24b3d8a5af41598b584d43c6913e1965d18e
6
+ metadata.gz: 6d1841d225856610228745312a60ae7e86cd175a40528902f2834aa1c51c548be68a74a481717127f8d4d15463670a4a7a80577983b7766ff841bcb51822a37c
7
+ data.tar.gz: 44dbd529f0b0d16ae0f3c115040a9b6879f85ef7e4da6668dbe869a10e5e2b752232828eec30b70292b1fd582e0876a8f9eeb420c81e6d05eb1ac22f9d1c8198
@@ -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
@@ -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
@@ -522,13 +533,13 @@ module Bunny
522
533
  end
523
534
 
524
535
  m = AMQ::Protocol::Basic::Publish.encode(@id,
525
- payload,
526
- meta,
527
- exchange_name,
528
- routing_key,
529
- meta[:mandatory],
530
- false,
531
- @connection.frame_max)
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
- delivery_tag,
769
- multiple,
770
- requeue))
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
- queue_name,
806
- consumer_tag,
807
- false,
808
- no_ack,
809
- exclusive,
810
- false,
811
- arguments))
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
- consumer.queue_name,
851
- consumer.consumer_tag,
852
- false,
853
- consumer.no_ack,
854
- consumer.exclusive,
855
- false,
856
- consumer.arguments))
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
- name,
931
- opts.fetch(:passive, false),
932
- opts.fetch(:durable, false),
933
- opts.fetch(:exclusive, false),
934
- opts.fetch(:auto_delete, false),
935
- false,
936
- opts[:arguments]))
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
- name,
960
- opts[:if_unused],
961
- opts[:if_empty],
962
- false))
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
- name,
1015
- exchange_name,
1016
- opts[:routing_key],
1017
- false,
1018
- opts[:arguments]))
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
- name,
1051
- exchange_name,
1052
- opts[:routing_key],
1053
- opts[:arguments]))
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
- name,
1086
- type.to_s,
1087
- opts.fetch(:passive, false),
1088
- opts.fetch(:durable, false),
1089
- opts.fetch(:auto_delete, false),
1090
- false,
1091
- false,
1092
- opts[:arguments]))
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
- name,
1116
- opts[:if_unused],
1117
- false))
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
- destination_name,
1158
- source_name,
1159
- opts[:routing_key],
1160
- false,
1161
- opts[:arguments]))
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
- destination_name,
1202
- source_name,
1203
- opts[:routing_key],
1204
- false,
1205
- opts[:arguments]))
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
@@ -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 = "1.0.0.pre2"
5
+ VERSION = "1.0.0.pre3"
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,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.pre2
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-07-26 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
@@ -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