bunny 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|