bunny 0.9.0.pre8 → 0.9.0.pre9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|