bunny 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +8 -0
- data/Gemfile +1 -1
- data/lib/bunny/exchange.rb +1 -1
- data/lib/bunny/reader_loop.rb +47 -18
- data/lib/bunny/session.rb +36 -17
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/connection_recovery_spec.rb +2 -2
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +9 -0
- data/spec/stress/connection_open_close_spec.rb +32 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b9d1b9674fc6fe77346de7896e4ac8900c4c047
|
4
|
+
data.tar.gz: 19972fd3c10baf9a7acf1bc2c49a43ed995baeaa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba09b21e9cbe72298decba6a551ecc554e17ff58c7f88a907979a4859453c7674341989588fd407d6923de40ebaa9e6af9cbbe31615e41d223a8c4163c5f89a0
|
7
|
+
data.tar.gz: df71d64b16826cfb887db8bcec84ecc114410cfaa92e9c97ca2d03cc003ffa7558998fcad6019031729c999888642ec82a69e2b00d10e3984b538dec61ebd800
|
data/ChangeLog.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## Changes between Bunny 1.2.1 and 1.2.2
|
2
|
+
|
3
|
+
### Synchronization Improvements for Session#close
|
4
|
+
|
5
|
+
`Bunny::Session#close` now better synchronizes state transitions,
|
6
|
+
eliminating a few race condition scenarios with I/O reader thread.
|
7
|
+
|
8
|
+
|
1
9
|
## Changes between Bunny 1.2.0 and 1.2.1
|
2
10
|
|
3
11
|
### Better Synchronization for Publisher Confirms
|
data/Gemfile
CHANGED
data/lib/bunny/exchange.rb
CHANGED
@@ -55,7 +55,7 @@ module Bunny
|
|
55
55
|
# @return [Exchange] An instance that corresponds to the default exchange (of type direct).
|
56
56
|
# @api public
|
57
57
|
def self.default(channel_or_connection)
|
58
|
-
self.new(
|
58
|
+
self.new(channel_or_connection, :direct, AMQ::Protocol::EMPTY_STRING, :no_declare => true)
|
59
59
|
end
|
60
60
|
|
61
61
|
# @param [Bunny::Channel] channel_or_connection Channel this exchange will use. {Bunny::Session} instances are supported only for
|
data/lib/bunny/reader_loop.rb
CHANGED
@@ -14,6 +14,8 @@ module Bunny
|
|
14
14
|
@session = session
|
15
15
|
@session_thread = session_thread
|
16
16
|
@logger = @session.logger
|
17
|
+
|
18
|
+
@mutex = Mutex.new
|
17
19
|
end
|
18
20
|
|
19
21
|
|
@@ -29,34 +31,37 @@ module Bunny
|
|
29
31
|
def run_loop
|
30
32
|
loop do
|
31
33
|
begin
|
32
|
-
break if @stopping || @network_is_down
|
34
|
+
break if @mutex.synchronize { @stopping || @stopped || @network_is_down }
|
33
35
|
run_once
|
34
|
-
rescue Errno::EBADF => ebadf
|
35
|
-
break if @stopping
|
36
|
-
# ignored, happens when we loop after the transport has already been closed
|
37
36
|
rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError => e
|
38
|
-
break if @
|
39
|
-
log_exception(e)
|
37
|
+
break if terminate? || @session.closing? || @session.closed?
|
40
38
|
|
39
|
+
log_exception(e)
|
41
40
|
@network_is_down = true
|
42
|
-
|
43
41
|
if @session.automatically_recover?
|
44
42
|
@session.handle_network_failure(e)
|
45
43
|
else
|
46
44
|
@session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
47
45
|
end
|
48
46
|
rescue ShutdownSignal => _
|
47
|
+
@mutex.synchronize { @stopping = true }
|
49
48
|
break
|
50
49
|
rescue Exception => e
|
51
|
-
break if
|
52
|
-
|
50
|
+
break if terminate?
|
51
|
+
if !(@session.closing? || @session.closed?)
|
52
|
+
log_exception(e)
|
53
53
|
|
54
|
-
|
55
|
-
|
54
|
+
@network_is_down = true
|
55
|
+
@session_thread.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
|
56
|
+
end
|
57
|
+
rescue Errno::EBADF => ebadf
|
58
|
+
break if terminate?
|
59
|
+
# ignored, happens when we loop after the transport has already been closed
|
60
|
+
@mutex.synchronize { @stopping = true }
|
56
61
|
end
|
57
62
|
end
|
58
63
|
|
59
|
-
@stopped = true
|
64
|
+
@mutex.synchronize { @stopped = true }
|
60
65
|
end
|
61
66
|
|
62
67
|
def run_once
|
@@ -83,11 +88,21 @@ module Bunny
|
|
83
88
|
end
|
84
89
|
|
85
90
|
def stop
|
86
|
-
@stopping = true
|
91
|
+
@mutex.synchronize { @stopping = true }
|
87
92
|
end
|
88
93
|
|
89
94
|
def stopped?
|
90
|
-
@stopped
|
95
|
+
@mutex.synchronize { @stopped = true }
|
96
|
+
end
|
97
|
+
|
98
|
+
def stopping?
|
99
|
+
@mutex.synchronize { @stopping = true }
|
100
|
+
end
|
101
|
+
|
102
|
+
def terminate_with(e)
|
103
|
+
@mutex.synchronize { @stopping = true }
|
104
|
+
|
105
|
+
self.raise(e)
|
91
106
|
end
|
92
107
|
|
93
108
|
def raise(e)
|
@@ -105,12 +120,26 @@ module Bunny
|
|
105
120
|
end
|
106
121
|
end
|
107
122
|
|
123
|
+
protected
|
124
|
+
|
108
125
|
def log_exception(e)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
126
|
+
if !(io_error?(e) && (@session.closing? || @session.closed?))
|
127
|
+
@logger.error "Exception in the reader loop: #{e.class.name}: #{e.message}"
|
128
|
+
@logger.error "Backtrace: "
|
129
|
+
e.backtrace.each do |line|
|
130
|
+
@logger.error "\t#{line}"
|
131
|
+
end
|
113
132
|
end
|
114
133
|
end
|
134
|
+
|
135
|
+
def io_error?(e)
|
136
|
+
[AMQ::Protocol::EmptyResponseError, IOError, SystemCallError].any? do |klazz|
|
137
|
+
e.is_a?(klazz)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def terminate?
|
142
|
+
@mutex.synchronize { @stopping || @stopped }
|
143
|
+
end
|
115
144
|
end
|
116
145
|
end
|
data/lib/bunny/session.rb
CHANGED
@@ -172,6 +172,7 @@ module Bunny
|
|
172
172
|
# transport operations/continuations mutex. A workaround for
|
173
173
|
# the non-reentrant Ruby mutexes. MK.
|
174
174
|
@transport_mutex = @mutex_impl.new
|
175
|
+
@status_mutex = @mutex_impl.new
|
175
176
|
@channels = Hash.new
|
176
177
|
|
177
178
|
@origin_thread = Thread.current
|
@@ -233,7 +234,7 @@ module Bunny
|
|
233
234
|
def start
|
234
235
|
return self if connected?
|
235
236
|
|
236
|
-
@status
|
237
|
+
@status_mutex.synchronize { @status = :connecting }
|
237
238
|
# reset here for cases when automatic network recovery kicks in
|
238
239
|
# when we were blocked. MK.
|
239
240
|
@blocked = false
|
@@ -259,7 +260,7 @@ module Bunny
|
|
259
260
|
|
260
261
|
@default_channel = self.create_channel
|
261
262
|
rescue Exception => e
|
262
|
-
@status = :not_connected
|
263
|
+
@status_mutex.synchronize { @status = :not_connected }
|
263
264
|
raise e
|
264
265
|
end
|
265
266
|
|
@@ -295,16 +296,18 @@ module Bunny
|
|
295
296
|
|
296
297
|
# Closes the connection. This involves closing all of its channels.
|
297
298
|
def close
|
298
|
-
|
299
|
-
|
299
|
+
@status_mutex.synchronize { @status = :closing }
|
300
|
+
|
301
|
+
ignoring_io_errors do
|
302
|
+
if @transport.open?
|
303
|
+
close_all_channels
|
300
304
|
|
301
|
-
Bunny::Timeout.timeout(@transport.disconnect_timeout, ClientTimeout) do
|
302
305
|
self.close_connection(true)
|
303
306
|
end
|
304
|
-
end
|
305
307
|
|
306
|
-
|
307
|
-
|
308
|
+
clean_up_on_shutdown
|
309
|
+
end
|
310
|
+
@status_mutex.synchronize { @status = :closed }
|
308
311
|
end
|
309
312
|
alias stop close
|
310
313
|
|
@@ -328,14 +331,22 @@ module Bunny
|
|
328
331
|
status == :connecting
|
329
332
|
end
|
330
333
|
|
334
|
+
# @return [Boolean] true if this AMQP 0.9.1 connection is closing
|
335
|
+
# @api private
|
336
|
+
def closing?
|
337
|
+
@status_mutex.synchronize { @status == :closing }
|
338
|
+
end
|
339
|
+
|
331
340
|
# @return [Boolean] true if this AMQP 0.9.1 connection is closed
|
332
341
|
def closed?
|
333
|
-
status == :closed
|
342
|
+
@status_mutex.synchronize { @status == :closed }
|
334
343
|
end
|
335
344
|
|
336
345
|
# @return [Boolean] true if this AMQP 0.9.1 connection is open
|
337
346
|
def open?
|
338
|
-
|
347
|
+
@status_mutex.synchronize do
|
348
|
+
(status == :open || status == :connected || status == :connecting) && @transport.open?
|
349
|
+
end
|
339
350
|
end
|
340
351
|
alias connected? open?
|
341
352
|
|
@@ -506,7 +517,7 @@ module Bunny
|
|
506
517
|
|
507
518
|
shut_down_all_consumer_work_pools!
|
508
519
|
maybe_shutdown_heartbeat_sender
|
509
|
-
@status
|
520
|
+
@status_mutex.synchronize { @status = :not_connected }
|
510
521
|
end
|
511
522
|
|
512
523
|
# Handles incoming frames and dispatches them.
|
@@ -597,7 +608,7 @@ module Bunny
|
|
597
608
|
def handle_network_failure(exception)
|
598
609
|
raise NetworkErrorWrapper.new(exception) unless @threaded
|
599
610
|
|
600
|
-
@status = :disconnected
|
611
|
+
@status_mutex.synchronize { @status = :disconnected }
|
601
612
|
|
602
613
|
if !recovering_from_network_failure?
|
603
614
|
begin
|
@@ -696,7 +707,7 @@ module Bunny
|
|
696
707
|
@continuations.push(method)
|
697
708
|
|
698
709
|
clean_up_on_shutdown
|
699
|
-
@origin_thread.
|
710
|
+
@origin_thread.terminate_with(@last_connection_error)
|
700
711
|
end
|
701
712
|
|
702
713
|
def clean_up_on_shutdown
|
@@ -793,7 +804,7 @@ module Bunny
|
|
793
804
|
if threaded?
|
794
805
|
# this is the easiest way to wait until the loop
|
795
806
|
# is guaranteed to have terminated
|
796
|
-
@reader_loop.
|
807
|
+
@reader_loop.terminate_with(ShutdownSignal)
|
797
808
|
# joining the thread here may take forever
|
798
809
|
# on JRuby because sun.nio.ch.KQueueArrayWrapper#kevent0 is
|
799
810
|
# a native method that cannot be (easily) interrupted.
|
@@ -928,7 +939,7 @@ module Bunny
|
|
928
939
|
@server_authentication_mechanisms = (connection_start.mechanisms || "").split(" ")
|
929
940
|
@server_locales = Array(connection_start.locales)
|
930
941
|
|
931
|
-
@status = :connected
|
942
|
+
@status_mutex.synchronize { @status = :connected }
|
932
943
|
end
|
933
944
|
|
934
945
|
# @private
|
@@ -1004,7 +1015,7 @@ module Bunny
|
|
1004
1015
|
end
|
1005
1016
|
connection_open_ok = frame2.decode_payload
|
1006
1017
|
|
1007
|
-
@status = :open
|
1018
|
+
@status_mutex.synchronize { @status = :open }
|
1008
1019
|
if @heartbeat && @heartbeat > 0
|
1009
1020
|
initialize_heartbeat_sender
|
1010
1021
|
end
|
@@ -1023,7 +1034,7 @@ module Bunny
|
|
1023
1034
|
close_transport
|
1024
1035
|
end
|
1025
1036
|
|
1026
|
-
@origin_thread.
|
1037
|
+
@origin_thread.terminate_with(e)
|
1027
1038
|
else
|
1028
1039
|
raise "could not open connection: server did not respond with connection.open-ok but #{connection_open_ok.inspect} instead"
|
1029
1040
|
end
|
@@ -1146,6 +1157,14 @@ module Bunny
|
|
1146
1157
|
n
|
1147
1158
|
end
|
1148
1159
|
end
|
1160
|
+
|
1161
|
+
def ignoring_io_errors(&block)
|
1162
|
+
begin
|
1163
|
+
block.call
|
1164
|
+
rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Bunny::NetworkFailure => _
|
1165
|
+
# ignore
|
1166
|
+
end
|
1167
|
+
end
|
1149
1168
|
end # Session
|
1150
1169
|
|
1151
1170
|
# backwards compatibility
|
data/lib/bunny/version.rb
CHANGED
@@ -29,7 +29,7 @@ unless ENV["CI"]
|
|
29
29
|
q.purge
|
30
30
|
x = ch.default_exchange
|
31
31
|
x.publish("msg", :routing_key => q.name)
|
32
|
-
sleep 0.
|
32
|
+
sleep 0.5
|
33
33
|
q.message_count.should == 1
|
34
34
|
q.purge
|
35
35
|
end
|
@@ -37,7 +37,7 @@ unless ENV["CI"]
|
|
37
37
|
def ensure_queue_binding_recovery(x, q, routing_key = "")
|
38
38
|
q.purge
|
39
39
|
x.publish("msg", :routing_key => routing_key)
|
40
|
-
sleep 0.
|
40
|
+
sleep 0.5
|
41
41
|
q.message_count.should == 1
|
42
42
|
q.purge
|
43
43
|
end
|
@@ -11,6 +11,15 @@ describe Bunny::Exchange do
|
|
11
11
|
connection.close
|
12
12
|
end
|
13
13
|
|
14
|
+
context "of default type" do
|
15
|
+
it "is declared with an empty name" do
|
16
|
+
ch = connection.create_channel
|
17
|
+
|
18
|
+
x = Bunny::Exchange.default(ch)
|
19
|
+
|
20
|
+
x.name.should == ''
|
21
|
+
end
|
22
|
+
end
|
14
23
|
|
15
24
|
context "of type fanout" do
|
16
25
|
context "with a non-predefined name" do
|
@@ -12,7 +12,7 @@ unless defined?(JRUBY_VERSION) && !ENV["FORCE_JRUBY_RUN"]
|
|
12
12
|
end
|
13
13
|
|
14
14
|
n.times do |i|
|
15
|
-
it "can be closed (take #{i})" do
|
15
|
+
it "can be closed (automatic recovery disabled, take #{i})" do
|
16
16
|
c = Bunny.new(:automatically_recover => false)
|
17
17
|
c.start
|
18
18
|
ch = c.create_channel
|
@@ -23,9 +23,39 @@ unless defined?(JRUBY_VERSION) && !ENV["FORCE_JRUBY_RUN"]
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
n.times do |i|
|
27
|
+
it "can be closed in the Hello, World example (take #{i})" do
|
28
|
+
c = Bunny.new(:automatically_recover => false)
|
29
|
+
c.start
|
30
|
+
ch = c.create_channel
|
31
|
+
x = ch.default_exchange
|
32
|
+
q = ch.queue("", :exclusive => true)
|
33
|
+
q.subscribe do |delivery_info, properties, payload|
|
34
|
+
# no-op
|
35
|
+
end
|
36
|
+
20.times { x.publish("hello", :routing_key => q.name) }
|
37
|
+
|
38
|
+
c.should be_connected
|
39
|
+
c.stop
|
40
|
+
c.should be_closed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
n.times do |i|
|
45
|
+
it "can be closed (automatic recovery enabled, take #{i})" do
|
46
|
+
c = Bunny.new(:automatically_recover => true)
|
47
|
+
c.start
|
48
|
+
ch = c.create_channel
|
49
|
+
|
50
|
+
c.should be_connected
|
51
|
+
c.stop
|
52
|
+
c.should be_closed
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
26
56
|
context "in the single threaded mode" do
|
27
57
|
n.times do |i|
|
28
|
-
it "can be closed (take #{i})" do
|
58
|
+
it "can be closed (single threaded mode, take #{i})" do
|
29
59
|
c = Bunny.new(:automatically_recover => false, :threaded => false)
|
30
60
|
c.start
|
31
61
|
ch = c.create_channel
|
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.2.
|
4
|
+
version: 1.2.2
|
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: 2014-
|
15
|
+
date: 2014-05-26 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: amq-protocol
|