bunny 1.0.0.pre2 → 1.0.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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