bunny 0.9.0.pre8 → 0.9.0.pre9
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.
- data/ChangeLog.md +31 -0
- data/README.md +1 -1
- data/examples/guides/extensions/dead_letter_exchange.rb +1 -1
- data/lib/bunny/channel.rb +9 -9
- data/lib/bunny/concurrent/continuation_queue.rb +28 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +56 -0
- data/lib/bunny/exceptions.rb +4 -0
- data/lib/bunny/heartbeat_sender.rb +3 -1
- data/lib/bunny/session.rb +61 -10
- data/lib/bunny/socket.rb +1 -1
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/basic_nack_spec.rb +1 -1
- data/spec/higher_level_api/integration/basic_publish_spec.rb +29 -0
- data/spec/higher_level_api/integration/connection_spec.rb +1 -1
- data/spec/higher_level_api/integration/dead_lettering_spec.rb +1 -1
- data/spec/higher_level_api/integration/heartbeat_spec.rb +31 -0
- data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +49 -0
- metadata +8 -2
data/ChangeLog.md
CHANGED
@@ -1,5 +1,36 @@
|
|
1
|
+
## Changes between Bunny 0.9.0.pre8 and 0.9.0.pre9
|
2
|
+
|
3
|
+
### More Reliable Heartbeat Sender
|
4
|
+
|
5
|
+
Heartbeat sender no longer slips into an infinite loop if it encounters an exception.
|
6
|
+
Instead, it will just stop (and presumably re-started when the network error recovery
|
7
|
+
kicks in or the app reconnects manually).
|
8
|
+
|
9
|
+
|
10
|
+
### Network Recovery After Delay
|
11
|
+
|
12
|
+
Network reconnection now kicks in after a delay to avoid aggressive
|
13
|
+
reconnections in situations when we don't want to endlessly reconnect
|
14
|
+
(e.g. when the connection was closed via the Management UI).
|
15
|
+
|
16
|
+
The `:network_recovery_interval` option passed to `Bunny::Session#initialize` and `Bunny.new`
|
17
|
+
controls the interval. Default is 5 seconds.
|
18
|
+
|
19
|
+
|
20
|
+
### Default Heartbeat Value Is Now Server-Defined
|
21
|
+
|
22
|
+
Bunny will now use heartbeat value provided by RabbitMQ by default.
|
23
|
+
|
24
|
+
|
25
|
+
|
1
26
|
## Changes between Bunny 0.9.0.pre7 and 0.9.0.pre8
|
2
27
|
|
28
|
+
### Stability Improvements
|
29
|
+
|
30
|
+
Several stability improvements in the network
|
31
|
+
layer, connection error handling, and concurrency hazards.
|
32
|
+
|
33
|
+
|
3
34
|
### Automatic Connection Recovery Can Be Disabled
|
4
35
|
|
5
36
|
Automatic connection recovery now can be disabled by passing
|
data/README.md
CHANGED
@@ -36,7 +36,7 @@ gem install bunny --pre
|
|
36
36
|
To use Bunny 0.9.x in a project managed with Bundler:
|
37
37
|
|
38
38
|
``` ruby
|
39
|
-
gem "bunny", ">= 0.9.0.
|
39
|
+
gem "bunny", ">= 0.9.0.pre8" # optionally: , :git => "git://github.com/ruby-amqp/bunny.git", :branch => "master"
|
40
40
|
```
|
41
41
|
|
42
42
|
|
@@ -23,7 +23,7 @@ sleep 0.2
|
|
23
23
|
delivery_info, _, _ = q.pop(:ack => true)
|
24
24
|
puts "#{dlq.message_count} messages dead lettered so far"
|
25
25
|
puts "Rejecting a message"
|
26
|
-
ch.nack(delivery_info.delivery_tag
|
26
|
+
ch.nack(delivery_info.delivery_tag)
|
27
27
|
sleep 0.2
|
28
28
|
puts "#{dlq.message_count} messages dead lettered so far"
|
29
29
|
|
data/lib/bunny/channel.rb
CHANGED
@@ -447,13 +447,13 @@ module Bunny
|
|
447
447
|
# supports rejecting multiple messages at once, and is usually preferred.
|
448
448
|
#
|
449
449
|
# @param [Integer] delivery_tag Delivery tag to reject
|
450
|
-
# @param [Boolean] requeue Should this message be requeued instead of dropping it?
|
451
450
|
# @param [Boolean] multiple (false) Should all unacknowledged messages up to this be rejected as well?
|
451
|
+
# @param [Boolean] requeue (false) Should this message be requeued instead of dropping it?
|
452
452
|
# @see Bunny::Channel#ack
|
453
453
|
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
454
454
|
# @api public
|
455
|
-
def nack(delivery_tag,
|
456
|
-
basic_nack(delivery_tag,
|
455
|
+
def nack(delivery_tag, multiple = false, requeue = false)
|
456
|
+
basic_nack(delivery_tag, multiple, requeue)
|
457
457
|
end
|
458
458
|
|
459
459
|
# @endgroup
|
@@ -713,7 +713,7 @@ module Bunny
|
|
713
713
|
# ch = conn.create_channel
|
714
714
|
# q.subscribe do |delivery_info, properties, payload|
|
715
715
|
# # requeue the message
|
716
|
-
# ch.basic_nack(delivery_info.delivery_tag, true)
|
716
|
+
# ch.basic_nack(delivery_info.delivery_tag, false, true)
|
717
717
|
# end
|
718
718
|
#
|
719
719
|
# @example Reject a message
|
@@ -723,7 +723,7 @@ module Bunny
|
|
723
723
|
# ch = conn.create_channel
|
724
724
|
# q.subscribe do |delivery_info, properties, payload|
|
725
725
|
# # requeue the message
|
726
|
-
# ch.basic_nack(delivery_info.delivery_tag
|
726
|
+
# ch.basic_nack(delivery_info.delivery_tag)
|
727
727
|
# end
|
728
728
|
#
|
729
729
|
# @example Requeue a message fetched via basic.get
|
@@ -733,7 +733,7 @@ module Bunny
|
|
733
733
|
# ch = conn.create_channel
|
734
734
|
# # we assume the queue exists and has messages
|
735
735
|
# delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
|
736
|
-
# ch.basic_nack(delivery_info.delivery_tag, true)
|
736
|
+
# ch.basic_nack(delivery_info.delivery_tag, false, true)
|
737
737
|
#
|
738
738
|
#
|
739
739
|
# @example Requeue multiple messages fetched via basic.get
|
@@ -751,12 +751,12 @@ module Bunny
|
|
751
751
|
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
752
752
|
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
753
753
|
# @api public
|
754
|
-
def basic_nack(delivery_tag,
|
754
|
+
def basic_nack(delivery_tag, multiple = false, requeue = false)
|
755
755
|
raise_if_no_longer_open!
|
756
756
|
@connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
|
757
757
|
delivery_tag,
|
758
|
-
|
759
|
-
|
758
|
+
multiple,
|
759
|
+
requeue))
|
760
760
|
|
761
761
|
nil
|
762
762
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Bunny
|
4
|
+
module Concurrent
|
5
|
+
class ContinuationQueue
|
6
|
+
def initialize(*args, &block)
|
7
|
+
@q = ::Queue.new(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def push(*args)
|
11
|
+
@q.push(*args)
|
12
|
+
end
|
13
|
+
alias << push
|
14
|
+
|
15
|
+
def pop
|
16
|
+
@q.pop
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear
|
20
|
+
@q.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(selector, *args, &block)
|
24
|
+
@q.__send__(selector, *args, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
if !defined?(JRUBY_VERSION)
|
2
|
+
raise "Bunny::Concurrent::LinkedContinuationQueue can only be used on JRuby!"
|
3
|
+
end
|
4
|
+
|
5
|
+
require "java"
|
6
|
+
|
7
|
+
java_import java.util.concurrent.LinkedBlockingQueue
|
8
|
+
java_import java.util.concurrent.TimeUnit
|
9
|
+
|
10
|
+
module Bunny
|
11
|
+
module Concurrent
|
12
|
+
# On JRuby, we'd rather use reliable and heavily battle tested j.u.c.
|
13
|
+
# primitives with well described semantics than informally specified, clumsy
|
14
|
+
# and limited Ruby standard library parts.
|
15
|
+
#
|
16
|
+
# This is an implementation of the continuation queue on top of the linked blocking
|
17
|
+
# queue in j.u.c.
|
18
|
+
#
|
19
|
+
# Compared to the Ruby standard library Queue, there is one limitation: you cannot
|
20
|
+
# push a nil on the queue, it will fail with a null pointer exception.
|
21
|
+
class LinkedContinuationQueue
|
22
|
+
def initialize(*args, &block)
|
23
|
+
@q = LinkedBlockingQueue.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def push(el, timeout_in_ms = nil)
|
27
|
+
if timeout_in_ms
|
28
|
+
@q.offer(el, timeout_in_ms, TimeUnit.MILLISECONDS)
|
29
|
+
else
|
30
|
+
@q.offer(el)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias << push
|
34
|
+
|
35
|
+
def pop
|
36
|
+
@q.take
|
37
|
+
end
|
38
|
+
|
39
|
+
def poll(timeout_in_ms = nil)
|
40
|
+
if timeout_in_ms
|
41
|
+
@q.poll(timeout_in_ms, TimeUnit.MILLISECONDS)
|
42
|
+
else
|
43
|
+
@q.poll
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear
|
48
|
+
@q.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(selector, *args, &block)
|
52
|
+
@q.__send__(selector, *args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/bunny/exceptions.rb
CHANGED
data/lib/bunny/session.rb
CHANGED
@@ -9,7 +9,11 @@ require "bunny/authentication/credentials_encoder"
|
|
9
9
|
require "bunny/authentication/plain_mechanism_encoder"
|
10
10
|
require "bunny/authentication/external_mechanism_encoder"
|
11
11
|
|
12
|
-
|
12
|
+
if defined?(JRUBY_VERSION)
|
13
|
+
require "bunny/concurrent/linked_continuation_queue"
|
14
|
+
else
|
15
|
+
require "bunny/concurrent/continuation_queue"
|
16
|
+
end
|
13
17
|
|
14
18
|
require "amq/protocol/client"
|
15
19
|
require "amq/settings"
|
@@ -28,7 +32,7 @@ module Bunny
|
|
28
32
|
# Default password used for connection
|
29
33
|
DEFAULT_PASSWORD = "guest"
|
30
34
|
# Default heartbeat interval, the same value as RabbitMQ 3.0 uses.
|
31
|
-
DEFAULT_HEARTBEAT =
|
35
|
+
DEFAULT_HEARTBEAT = :server
|
32
36
|
# @private
|
33
37
|
DEFAULT_FRAME_MAX = 131072
|
34
38
|
|
@@ -52,6 +56,8 @@ module Bunny
|
|
52
56
|
|
53
57
|
DEFAULT_LOCALE = "en_GB"
|
54
58
|
|
59
|
+
DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
|
60
|
+
|
55
61
|
|
56
62
|
#
|
57
63
|
# API
|
@@ -107,6 +113,7 @@ module Bunny
|
|
107
113
|
else
|
108
114
|
opts[:automatically_recover] || opts[:automatic_recovery]
|
109
115
|
end
|
116
|
+
@network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
|
110
117
|
|
111
118
|
@status = :not_connected
|
112
119
|
|
@@ -124,7 +131,7 @@ module Bunny
|
|
124
131
|
@network_mutex = Mutex.new
|
125
132
|
@channels = Hash.new
|
126
133
|
|
127
|
-
|
134
|
+
self.reset_continuations
|
128
135
|
end
|
129
136
|
|
130
137
|
# @return [String] RabbitMQ hostname (or IP address) used
|
@@ -136,6 +143,9 @@ module Bunny
|
|
136
143
|
# @return [String] Virtual host used
|
137
144
|
def virtual_host; self.vhost; end
|
138
145
|
|
146
|
+
# @return [Integer] Heartbeat interval used
|
147
|
+
def heartbeat_interval; self.heartbeat; end
|
148
|
+
|
139
149
|
# @return [Boolean] true if this connection uses TLS (SSL)
|
140
150
|
def uses_tls?
|
141
151
|
@transport.uses_tls?
|
@@ -148,11 +158,18 @@ module Bunny
|
|
148
158
|
end
|
149
159
|
alias ssl? uses_ssl?
|
150
160
|
|
161
|
+
# @return [Boolean] true if this connection uses a separate thread for I/O activity
|
162
|
+
def threaded?
|
163
|
+
@threaded
|
164
|
+
end
|
165
|
+
|
151
166
|
# Starts connection process
|
152
167
|
# @api public
|
153
168
|
def start
|
154
|
-
|
169
|
+
return self if connected?
|
170
|
+
|
155
171
|
@status = :connecting
|
172
|
+
self.reset_continuations
|
156
173
|
|
157
174
|
self.initialize_transport
|
158
175
|
|
@@ -163,6 +180,8 @@ module Bunny
|
|
163
180
|
self.start_main_loop if @threaded
|
164
181
|
|
165
182
|
@default_channel = self.create_channel
|
183
|
+
|
184
|
+
self
|
166
185
|
end
|
167
186
|
|
168
187
|
def read_write_timeout
|
@@ -197,6 +216,10 @@ module Bunny
|
|
197
216
|
end
|
198
217
|
alias stop close
|
199
218
|
|
219
|
+
# Creates a temporary channel, yields it to the block given to this
|
220
|
+
# method and closes it.
|
221
|
+
#
|
222
|
+
# @return [Bunny::Session] self
|
200
223
|
def with_channel(n = nil)
|
201
224
|
ch = create_channel(n)
|
202
225
|
yield ch
|
@@ -205,7 +228,7 @@ module Bunny
|
|
205
228
|
self
|
206
229
|
end
|
207
230
|
|
208
|
-
|
231
|
+
# @return [Boolean] true if this connection is still not fully open
|
209
232
|
def connecting?
|
210
233
|
status == :connecting
|
211
234
|
end
|
@@ -215,10 +238,11 @@ module Bunny
|
|
215
238
|
end
|
216
239
|
|
217
240
|
def open?
|
218
|
-
(status == :open || status == :connected) && @transport.open?
|
241
|
+
(status == :open || status == :connected || status == :connecting) && @transport.open?
|
219
242
|
end
|
220
243
|
alias connected? open?
|
221
244
|
|
245
|
+
# @return [Boolean] true if this connection has automatic recovery from network failure enabled
|
222
246
|
def automatically_recover?
|
223
247
|
@automatically_recover
|
224
248
|
end
|
@@ -318,6 +342,8 @@ module Bunny
|
|
318
342
|
when AMQ::Protocol::Connection::Close then
|
319
343
|
@last_connection_error = instantiate_connection_level_exception(method)
|
320
344
|
@continuations.push(method)
|
345
|
+
|
346
|
+
raise @last_connection_error
|
321
347
|
when AMQ::Protocol::Connection::CloseOk then
|
322
348
|
@last_connection_close_ok = method
|
323
349
|
begin
|
@@ -332,7 +358,7 @@ module Bunny
|
|
332
358
|
puts e.message
|
333
359
|
puts e.backtrace
|
334
360
|
ensure
|
335
|
-
@continuations.push(
|
361
|
+
@continuations.push(:__unblock__)
|
336
362
|
end
|
337
363
|
when AMQ::Protocol::Channel::Close then
|
338
364
|
begin
|
@@ -376,6 +402,8 @@ module Bunny
|
|
376
402
|
def handle_network_failure(exception)
|
377
403
|
raise NetworkErrorWrapper.new(exception) unless @threaded
|
378
404
|
|
405
|
+
@status = :disconnected
|
406
|
+
|
379
407
|
if !recovering_from_network_failure?
|
380
408
|
@recovering_from_network_failure = true
|
381
409
|
if recoverable_network_failure?(exception)
|
@@ -406,6 +434,7 @@ module Bunny
|
|
406
434
|
# @private
|
407
435
|
def recover_from_network_failure
|
408
436
|
begin
|
437
|
+
sleep @network_recovery_interval
|
409
438
|
# puts "About to start recovery..."
|
410
439
|
start
|
411
440
|
|
@@ -416,7 +445,7 @@ module Bunny
|
|
416
445
|
end
|
417
446
|
rescue TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e
|
418
447
|
# puts "TCP connection failed, reconnecting in 5 seconds"
|
419
|
-
sleep
|
448
|
+
sleep @network_recovery_interval
|
420
449
|
retry if recoverable_network_failure?(e)
|
421
450
|
end
|
422
451
|
end
|
@@ -443,11 +472,13 @@ module Bunny
|
|
443
472
|
case frame
|
444
473
|
when AMQ::Protocol::Connection::Close then
|
445
474
|
klass = case frame.reply_code
|
475
|
+
when 320 then
|
476
|
+
ConnectionForced
|
446
477
|
when 503 then
|
447
478
|
InvalidCommand
|
448
479
|
when 504 then
|
449
480
|
ChannelError
|
450
|
-
when
|
481
|
+
when 505 then
|
451
482
|
UnexpectedFrame
|
452
483
|
else
|
453
484
|
raise "Unknown reply code: #{frame.reply_code}, text: #{frame.reply_text}"
|
@@ -530,6 +561,11 @@ module Bunny
|
|
530
561
|
@event_loop ||= MainLoop.new(@transport, self, Thread.current)
|
531
562
|
end
|
532
563
|
|
564
|
+
# @private
|
565
|
+
def maybe_shutdown_main_loop
|
566
|
+
@event_loop.stop if @event_loop
|
567
|
+
end
|
568
|
+
|
533
569
|
# @private
|
534
570
|
def signal_activity!
|
535
571
|
@heartbeat_sender.signal_activity! if @heartbeat_sender
|
@@ -633,7 +669,7 @@ module Bunny
|
|
633
669
|
@frame_max = negotiate_value(@client_frame_max, connection_tune.frame_max)
|
634
670
|
@channel_max = negotiate_value(@client_channel_max, connection_tune.channel_max)
|
635
671
|
# this allows for disabled heartbeats. MK.
|
636
|
-
@heartbeat = if
|
672
|
+
@heartbeat = if heartbeat_disabled?(@client_heartbeat)
|
637
673
|
0
|
638
674
|
else
|
639
675
|
negotiate_value(@client_heartbeat, connection_tune.heartbeat)
|
@@ -665,8 +701,14 @@ module Bunny
|
|
665
701
|
raise "could not open connection: server did not respond with connection.open-ok" unless connection_open_ok.is_a?(AMQ::Protocol::Connection::OpenOk)
|
666
702
|
end
|
667
703
|
|
704
|
+
def heartbeat_disabled?(val)
|
705
|
+
0 == val || val.nil?
|
706
|
+
end
|
707
|
+
|
668
708
|
# @api private
|
669
709
|
def negotiate_value(client_value, server_value)
|
710
|
+
return server_value if client_value == :server
|
711
|
+
|
670
712
|
if client_value == 0 || server_value == 0
|
671
713
|
[client_value, server_value].max
|
672
714
|
else
|
@@ -708,6 +750,15 @@ module Bunny
|
|
708
750
|
Authentication::CredentialsEncoder.for_session(self)
|
709
751
|
end
|
710
752
|
|
753
|
+
# @api private
|
754
|
+
def reset_continuations
|
755
|
+
@continuations = if defined?(JRUBY_VERSION)
|
756
|
+
Concurrent::LinkedContinuationQueue.new
|
757
|
+
else
|
758
|
+
Concurrent::ContinuationQueue.new
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
711
762
|
# @api private
|
712
763
|
def wait_on_continuations
|
713
764
|
unless @threaded
|
data/lib/bunny/socket.rb
CHANGED
@@ -14,7 +14,7 @@ module Bunny
|
|
14
14
|
if Socket.constants.include?('TCP_NODELAY') || Socket.constants.include?(:TCP_NODELAY)
|
15
15
|
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
|
16
16
|
end
|
17
|
-
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options
|
17
|
+
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
|
18
18
|
sock.options = {:host => host, :port => port}.merge(options)
|
19
19
|
sock
|
20
20
|
end
|
data/lib/bunny/version.rb
CHANGED
@@ -14,6 +14,7 @@ describe "Publishing a message to the default exchange" do
|
|
14
14
|
|
15
15
|
context "with all default delivery and message properties" do
|
16
16
|
it "routes messages to a queue with the same name as the routing key" do
|
17
|
+
connection.should be_threaded
|
17
18
|
ch = connection.create_channel
|
18
19
|
|
19
20
|
q = ch.queue("", :exclusive => true)
|
@@ -34,6 +35,7 @@ describe "Publishing a message to the default exchange" do
|
|
34
35
|
|
35
36
|
context "with all default delivery and message properties" do
|
36
37
|
it "routes the messages and preserves all the metadata" do
|
38
|
+
connection.should be_threaded
|
37
39
|
ch = connection.create_channel
|
38
40
|
|
39
41
|
q = ch.queue("", :exclusive => true)
|
@@ -55,4 +57,31 @@ describe "Publishing a message to the default exchange" do
|
|
55
57
|
ch.close
|
56
58
|
end
|
57
59
|
end
|
60
|
+
|
61
|
+
|
62
|
+
context "with all default delivery and message properties on a single-threaded connection" do
|
63
|
+
let(:connection) do
|
64
|
+
c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :threaded => false)
|
65
|
+
c.start
|
66
|
+
c
|
67
|
+
end
|
68
|
+
|
69
|
+
it "routes messages to a queue with the same name as the routing key" do
|
70
|
+
connection.should_not be_threaded
|
71
|
+
ch = connection.create_channel
|
72
|
+
|
73
|
+
q = ch.queue("", :exclusive => true)
|
74
|
+
x = ch.default_exchange
|
75
|
+
|
76
|
+
x.publish("xyzzy", :routing_key => q.name).
|
77
|
+
publish("xyzzy", :routing_key => q.name).
|
78
|
+
publish("xyzzy", :routing_key => q.name).
|
79
|
+
publish("xyzzy", :routing_key => q.name)
|
80
|
+
|
81
|
+
sleep(1)
|
82
|
+
q.message_count.should == 4
|
83
|
+
|
84
|
+
ch.close
|
85
|
+
end
|
86
|
+
end
|
58
87
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Client-defined heartbeat interval" do
|
4
|
+
let(:connection) do
|
5
|
+
c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :heartbeat_interval => 4)
|
6
|
+
c.start
|
7
|
+
c
|
8
|
+
end
|
9
|
+
|
10
|
+
it "can be enabled explicitly" do
|
11
|
+
sleep 5.0
|
12
|
+
|
13
|
+
connection.close
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
describe "Server-defined heartbeat interval" do
|
19
|
+
let(:connection) do
|
20
|
+
c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :heartbeat_interval => :server)
|
21
|
+
c.start
|
22
|
+
c
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can be enabled explicitly" do
|
26
|
+
puts "Sleeping for 5 seconds with heartbeat interval of 4"
|
27
|
+
sleep 5.0
|
28
|
+
|
29
|
+
connection.close
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Rapidly opening and closing lots of channels on a non-threaded connection" do
|
4
|
+
let(:connection) do
|
5
|
+
c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :automatic_recovery => false, :threaded => false)
|
6
|
+
c.start
|
7
|
+
c
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
connection.close
|
12
|
+
end
|
13
|
+
|
14
|
+
context "in a single-threaded scenario" do
|
15
|
+
let(:n) { 500 }
|
16
|
+
|
17
|
+
it "works correctly" do
|
18
|
+
xs = Array.new(n) { connection.create_channel }
|
19
|
+
puts "Opened #{n} channels"
|
20
|
+
|
21
|
+
xs.size.should == n
|
22
|
+
xs.each do |ch|
|
23
|
+
ch.close
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "in a multi-threaded scenario" do
|
29
|
+
# actually, on MRI values greater than ~100 will eventually cause write
|
30
|
+
# operations to fail with a timeout (1 second is not enough)
|
31
|
+
# which will cause recovery to re-acquire @channel_mutex in Session.
|
32
|
+
# Because Ruby's mutexes are not re-entrant, it will raise a ThreadError.
|
33
|
+
#
|
34
|
+
# But this already demonstrates that within these platform constraints,
|
35
|
+
# Bunny is safe to use in such scenarios.
|
36
|
+
let(:n) { 50 }
|
37
|
+
|
38
|
+
it "works correctly" do
|
39
|
+
n.times do
|
40
|
+
t = Thread.new do
|
41
|
+
ch = connection.create_channel
|
42
|
+
|
43
|
+
ch.close
|
44
|
+
end
|
45
|
+
t.abort_on_exception = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bunny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.0.
|
4
|
+
version: 0.9.0.pre9
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2013-
|
16
|
+
date: 2013-04-22 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: amq-protocol
|
@@ -93,6 +93,8 @@ files:
|
|
93
93
|
- lib/bunny/channel_id_allocator.rb
|
94
94
|
- lib/bunny/compatibility.rb
|
95
95
|
- lib/bunny/concurrent/condition.rb
|
96
|
+
- lib/bunny/concurrent/continuation_queue.rb
|
97
|
+
- lib/bunny/concurrent/linked_continuation_queue.rb
|
96
98
|
- lib/bunny/consumer.rb
|
97
99
|
- lib/bunny/consumer_tag_generator.rb
|
98
100
|
- lib/bunny/consumer_work_pool.rb
|
@@ -134,6 +136,7 @@ files:
|
|
134
136
|
- spec/higher_level_api/integration/exchange_declare_spec.rb
|
135
137
|
- spec/higher_level_api/integration/exchange_delete_spec.rb
|
136
138
|
- spec/higher_level_api/integration/exchange_unbind_spec.rb
|
139
|
+
- spec/higher_level_api/integration/heartbeat_spec.rb
|
137
140
|
- spec/higher_level_api/integration/message_properties_access_spec.rb
|
138
141
|
- spec/higher_level_api/integration/predeclared_exchanges_spec.rb
|
139
142
|
- spec/higher_level_api/integration/publisher_confirms_spec.rb
|
@@ -155,6 +158,7 @@ files:
|
|
155
158
|
- spec/lower_level_api/integration/basic_consume_spec.rb
|
156
159
|
- spec/spec_helper.rb
|
157
160
|
- spec/stress/channel_open_stress_spec.rb
|
161
|
+
- spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb
|
158
162
|
- spec/stress/concurrent_consumers_stress_spec.rb
|
159
163
|
- spec/stress/concurrent_publishers_stress_spec.rb
|
160
164
|
- spec/unit/bunny_spec.rb
|
@@ -211,6 +215,7 @@ test_files:
|
|
211
215
|
- spec/higher_level_api/integration/exchange_declare_spec.rb
|
212
216
|
- spec/higher_level_api/integration/exchange_delete_spec.rb
|
213
217
|
- spec/higher_level_api/integration/exchange_unbind_spec.rb
|
218
|
+
- spec/higher_level_api/integration/heartbeat_spec.rb
|
214
219
|
- spec/higher_level_api/integration/message_properties_access_spec.rb
|
215
220
|
- spec/higher_level_api/integration/predeclared_exchanges_spec.rb
|
216
221
|
- spec/higher_level_api/integration/publisher_confirms_spec.rb
|
@@ -232,6 +237,7 @@ test_files:
|
|
232
237
|
- spec/lower_level_api/integration/basic_consume_spec.rb
|
233
238
|
- spec/spec_helper.rb
|
234
239
|
- spec/stress/channel_open_stress_spec.rb
|
240
|
+
- spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb
|
235
241
|
- spec/stress/concurrent_consumers_stress_spec.rb
|
236
242
|
- spec/stress/concurrent_publishers_stress_spec.rb
|
237
243
|
- spec/unit/bunny_spec.rb
|