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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c8f1c8052f218b6d4ed95579a93e03ee9014fea5
4
- data.tar.gz: 739d9b25a80d0c6063124229fe8226fe45390ed3
3
+ metadata.gz: 0b9d1b9674fc6fe77346de7896e4ac8900c4c047
4
+ data.tar.gz: 19972fd3c10baf9a7acf1bc2c49a43ed995baeaa
5
5
  SHA512:
6
- metadata.gz: 0fe50e8b79c8611edf5f6b3f152b21680fdcb9790bf73126a2c3e4e2bbc38d485b26cef1edf0bde99eca2e8d34c784776396cd4cf8a26829b53ca7974de45430
7
- data.tar.gz: 2cb6bf110f3c9508eb055e37c2aebac6ff0915e6d89e014934738b9671041e6b34a9af13aca165d057d296a73efd6f2497efaaa4c0049726b9a362cf31c26e35
6
+ metadata.gz: ba09b21e9cbe72298decba6a551ecc554e17ff58c7f88a907979a4859453c7674341989588fd407d6923de40ebaa9e6af9cbbe31615e41d223a8c4163c5f89a0
7
+ data.tar.gz: df71d64b16826cfb887db8bcec84ecc114410cfaa92e9c97ca2d03cc003ffa7558998fcad6019031729c999888642ec82a69e2b00d10e3984b538dec61ebd800
@@ -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
@@ -35,7 +35,7 @@ end
35
35
 
36
36
  group :test do
37
37
  gem "rspec", ">= 2.13.0"
38
- gem "rabbitmq_http_api_client", "~> 1.0.0"
38
+ gem "rabbitmq_http_api_client", "~> 1.1.0"
39
39
  end
40
40
 
41
41
  gemspec
@@ -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(channel_from(channel_or_connection), :direct, AMQ::Protocol::EMPTY_STRING, :no_declare => true)
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
@@ -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 @stopping
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 @stopping
52
- log_exception(e)
50
+ break if terminate?
51
+ if !(@session.closing? || @session.closed?)
52
+ log_exception(e)
53
53
 
54
- @network_is_down = true
55
- @session_thread.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
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
- @logger.error "Exception in the reader loop: #{e.class.name}: #{e.message}"
110
- @logger.error "Backtrace: "
111
- e.backtrace.each do |line|
112
- @logger.error "\t#{line}"
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
@@ -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 = :connecting
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
- if @transport.open?
299
- close_all_channels
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
- clean_up_on_shutdown
307
- @status = :closed
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
- (status == :open || status == :connected || status == :connecting) && @transport.open?
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 = :not_connected
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.raise(@last_connection_error)
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.raise(ShutdownSignal)
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.raise(e)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "1.2.1"
5
+ VERSION = "1.2.2"
6
6
  end
@@ -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.2
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.2
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.1
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-04-11 00:00:00.000000000 Z
15
+ date: 2014-05-26 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol