bunny 0.9.0.pre10 → 0.9.0.pre11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-version +1 -1
- data/.travis.yml +4 -7
- data/ChangeLog.md +79 -0
- data/Gemfile +3 -1
- data/README.md +1 -1
- data/benchmarks/basic_publish/with_128K_messages.rb +35 -0
- data/benchmarks/basic_publish/with_1k_messages.rb +35 -0
- data/benchmarks/basic_publish/with_4K_messages.rb +35 -0
- data/benchmarks/basic_publish/with_64K_messages.rb +35 -0
- data/benchmarks/channel_open.rb +28 -0
- data/benchmarks/queue_declare.rb +29 -0
- data/benchmarks/queue_declare_and_bind.rb +29 -0
- data/benchmarks/queue_declare_bind_and_delete.rb +29 -0
- data/benchmarks/write_vs_write_nonblock.rb +49 -0
- data/bunny.gemspec +3 -3
- data/lib/bunny.rb +2 -0
- data/lib/bunny/channel.rb +31 -35
- data/lib/bunny/concurrent/continuation_queue.rb +10 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +4 -2
- data/lib/bunny/exceptions.rb +5 -2
- data/lib/bunny/heartbeat_sender.rb +6 -4
- data/lib/bunny/queue.rb +3 -0
- data/lib/bunny/{main_loop.rb → reader_loop.rb} +5 -8
- data/lib/bunny/session.rb +93 -48
- data/lib/bunny/socket.rb +37 -3
- data/lib/bunny/test_kit.rb +26 -0
- data/lib/bunny/transport.rb +39 -33
- data/lib/bunny/version.rb +1 -1
- data/profiling/basic_publish/with_4K_messages.rb +33 -0
- data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +10 -11
- data/spec/higher_level_api/integration/queue_declare_spec.rb +55 -13
- data/spec/issues/issue100_spec.rb +29 -27
- data/spec/issues/issue78_spec.rb +54 -52
- data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -22
- data/spec/stress/concurrent_consumers_stress_spec.rb +2 -1
- data/spec/stress/concurrent_publishers_stress_spec.rb +7 -10
- data/spec/stress/long_running_consumer_spec.rb +83 -0
- data/spec/unit/concurrent/condition_spec.rb +7 -5
- data/spec/unit/concurrent/linked_continuation_queue_spec.rb +35 -0
- metadata +48 -44
data/bunny.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.name = "bunny"
|
9
9
|
s.version = Bunny::VERSION.dup
|
10
10
|
s.homepage = "http://github.com/ruby-amqp/bunny"
|
11
|
-
s.summary = "
|
12
|
-
s.description = "
|
11
|
+
s.summary = "Popular easy to use Ruby client for RabbitMQ"
|
12
|
+
s.description = "Popular easy to use Ruby client for RabbitMQ"
|
13
13
|
s.license = "MIT"
|
14
14
|
|
15
15
|
# Sorted alphabetically.
|
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
map { |mail| Base64.decode64(mail) }
|
30
30
|
|
31
31
|
# Dependencies
|
32
|
-
s.add_dependency "amq-protocol", ">= 1.
|
32
|
+
s.add_dependency "amq-protocol", ">= 1.6.0"
|
33
33
|
|
34
34
|
# Files.
|
35
35
|
s.has_rdoc = true
|
data/lib/bunny.rb
CHANGED
data/lib/bunny/channel.rb
CHANGED
@@ -159,6 +159,7 @@ module Bunny
|
|
159
159
|
# @param [Bunny::ConsumerWorkPool] work_pool Thread pool for delivery processing, by default of size 1
|
160
160
|
def initialize(connection = nil, id = nil, work_pool = ConsumerWorkPool.new(1))
|
161
161
|
@connection = connection
|
162
|
+
@logger = connection.logger
|
162
163
|
@id = id || @connection.next_channel_id
|
163
164
|
@status = :opening
|
164
165
|
|
@@ -214,6 +215,7 @@ module Bunny
|
|
214
215
|
def close
|
215
216
|
@connection.close_channel(self)
|
216
217
|
closed!
|
218
|
+
maybe_kill_consumer_work_pool!
|
217
219
|
end
|
218
220
|
|
219
221
|
# @return [Boolean] true if this channel is open, false otherwise
|
@@ -1281,7 +1283,7 @@ module Bunny
|
|
1281
1283
|
raise_if_no_longer_open!
|
1282
1284
|
|
1283
1285
|
if @next_publish_seq_no == 0
|
1284
|
-
@confirms_continuations =
|
1286
|
+
@confirms_continuations = new_continuation
|
1285
1287
|
@unconfirmed_set = Set.new
|
1286
1288
|
@nacked_set = Set.new
|
1287
1289
|
@next_publish_seq_no = 1
|
@@ -1358,7 +1360,7 @@ module Bunny
|
|
1358
1360
|
#
|
1359
1361
|
# @api plugin
|
1360
1362
|
def recover_from_network_failure
|
1361
|
-
|
1363
|
+
@logger.debug "Recovering channel #{@id} after network failure"
|
1362
1364
|
release_all_continuations
|
1363
1365
|
|
1364
1366
|
recover_prefetch_setting
|
@@ -1392,7 +1394,7 @@ module Bunny
|
|
1392
1394
|
# @api plugin
|
1393
1395
|
def recover_queues
|
1394
1396
|
@queues.values.dup.each do |q|
|
1395
|
-
|
1397
|
+
@logger.debug "Recovering queue #{q.name}"
|
1396
1398
|
q.recover_from_network_failure
|
1397
1399
|
end
|
1398
1400
|
end
|
@@ -1436,7 +1438,7 @@ module Bunny
|
|
1436
1438
|
|
1437
1439
|
# @private
|
1438
1440
|
def handle_method(method)
|
1439
|
-
|
1441
|
+
@logger.debug "Channel#handle_frame on channel #{@id}: #{method.inspect}"
|
1440
1442
|
case method
|
1441
1443
|
when AMQ::Protocol::Queue::DeclareOk then
|
1442
1444
|
@continuations.push(method)
|
@@ -1525,8 +1527,7 @@ module Bunny
|
|
1525
1527
|
consumer.call(DeliveryInfo.new(basic_deliver), MessageProperties.new(properties), content)
|
1526
1528
|
end
|
1527
1529
|
else
|
1528
|
-
#
|
1529
|
-
puts "[warning] No consumer for tag #{basic_deliver.consumer_tag}"
|
1530
|
+
@logger.warn "No consumer for tag #{basic_deliver.consumer_tag} on channel #{@id}!"
|
1530
1531
|
end
|
1531
1532
|
end
|
1532
1533
|
|
@@ -1537,7 +1538,7 @@ module Bunny
|
|
1537
1538
|
if x
|
1538
1539
|
x.handle_return(ReturnInfo.new(basic_return), MessageProperties.new(properties), content)
|
1539
1540
|
else
|
1540
|
-
#
|
1541
|
+
@logger.warn "Exchange #{basic_return.exchange} is not in channel #{@id}'s cache! Dropping returned message!"
|
1541
1542
|
end
|
1542
1543
|
end
|
1543
1544
|
|
@@ -1574,12 +1575,13 @@ module Bunny
|
|
1574
1575
|
t = Thread.current
|
1575
1576
|
@threads_waiting_on_continuations << t
|
1576
1577
|
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1578
|
+
begin
|
1579
|
+
@continuations.poll(@connection.continuation_timeout)
|
1580
|
+
ensure
|
1581
|
+
@threads_waiting_on_continuations.delete(t)
|
1582
|
+
end
|
1581
1583
|
else
|
1582
|
-
connection.
|
1584
|
+
connection.reader_loop.run_once until @continuations.length > 0
|
1583
1585
|
|
1584
1586
|
@continuations.pop
|
1585
1587
|
end
|
@@ -1591,10 +1593,11 @@ module Bunny
|
|
1591
1593
|
t = Thread.current
|
1592
1594
|
@threads_waiting_on_basic_get_continuations << t
|
1593
1595
|
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1596
|
+
begin
|
1597
|
+
@basic_get_continuations.poll(@connection.continuation_timeout)
|
1598
|
+
ensure
|
1599
|
+
@threads_waiting_on_basic_get_continuations.delete(t)
|
1600
|
+
end
|
1598
1601
|
else
|
1599
1602
|
connection.event_loop.run_once until @basic_get_continuations.length > 0
|
1600
1603
|
|
@@ -1608,10 +1611,11 @@ module Bunny
|
|
1608
1611
|
t = Thread.current
|
1609
1612
|
@threads_waiting_on_confirms_continuations << t
|
1610
1613
|
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1614
|
+
begin
|
1615
|
+
@confirms_continuations.poll(@connection.continuation_timeout)
|
1616
|
+
ensure
|
1617
|
+
@threads_waiting_on_confirms_continuations.delete(t)
|
1618
|
+
end
|
1615
1619
|
else
|
1616
1620
|
connection.event_loop.run_once until @confirms_continuations.length > 0
|
1617
1621
|
|
@@ -1622,25 +1626,17 @@ module Bunny
|
|
1622
1626
|
# Releases all continuations. Used by automatic network recovery.
|
1623
1627
|
# @private
|
1624
1628
|
def release_all_continuations
|
1625
|
-
|
1626
|
-
|
1627
|
-
t.run
|
1628
|
-
end
|
1629
|
+
@threads_waiting_on_confirms_continuations.each do |t|
|
1630
|
+
t.run
|
1629
1631
|
end
|
1630
|
-
|
1631
|
-
|
1632
|
-
t.run
|
1633
|
-
end
|
1632
|
+
@threads_waiting_on_continuations.each do |t|
|
1633
|
+
t.run
|
1634
1634
|
end
|
1635
|
-
|
1636
|
-
|
1637
|
-
t.run
|
1638
|
-
end
|
1635
|
+
@threads_waiting_on_basic_get_continuations.each do |t|
|
1636
|
+
t.run
|
1639
1637
|
end
|
1640
1638
|
|
1641
|
-
|
1642
|
-
@confirms_continuations = ::Queue.new
|
1643
|
-
@basic_get_continuations = ::Queue.new
|
1639
|
+
self.reset_continuations
|
1644
1640
|
end
|
1645
1641
|
|
1646
1642
|
# Starts consumer work pool. Lazily called by #basic_consume to avoid creating new threads
|
@@ -25,7 +25,7 @@ module Bunny
|
|
25
25
|
|
26
26
|
def push(el, timeout_in_ms = nil)
|
27
27
|
if timeout_in_ms
|
28
|
-
@q.offer(el, timeout_in_ms, TimeUnit
|
28
|
+
@q.offer(el, timeout_in_ms, TimeUnit::MILLISECONDS)
|
29
29
|
else
|
30
30
|
@q.offer(el)
|
31
31
|
end
|
@@ -38,7 +38,9 @@ module Bunny
|
|
38
38
|
|
39
39
|
def poll(timeout_in_ms = nil)
|
40
40
|
if timeout_in_ms
|
41
|
-
@q.poll(timeout_in_ms, TimeUnit
|
41
|
+
v = @q.poll(timeout_in_ms, TimeUnit::MILLISECONDS)
|
42
|
+
raise ::Timeout::Error.new("operation did not finish in #{timeout_in_ms} ms") if v.nil?
|
43
|
+
v
|
42
44
|
else
|
43
45
|
@q.poll
|
44
46
|
end
|
data/lib/bunny/exceptions.rb
CHANGED
@@ -98,7 +98,7 @@ module Bunny
|
|
98
98
|
# Raised by adapters when frame does not end with {final octet AMQ::Protocol::Frame::FINAL_OCTET}.
|
99
99
|
# This suggest that there is a bug in adapter or AMQ broker implementation.
|
100
100
|
#
|
101
|
-
# @see
|
101
|
+
# @see https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.3)
|
102
102
|
class NoFinalOctetError < InconsistentDataError
|
103
103
|
def initialize
|
104
104
|
super("Frame doesn't end with #{AMQ::Protocol::Frame::FINAL_OCTET} as it must, which means the size is miscalculated.")
|
@@ -109,7 +109,7 @@ module Bunny
|
|
109
109
|
# to the size specified in that frame's header.
|
110
110
|
# This suggest that there is a bug in adapter or AMQ broker implementation.
|
111
111
|
#
|
112
|
-
# @see
|
112
|
+
# @see https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.3)
|
113
113
|
class BadLengthError < InconsistentDataError
|
114
114
|
def initialize(expected_length, actual_length)
|
115
115
|
super("Frame payload should be #{expected_length} long, but it's #{actual_length} long.")
|
@@ -146,6 +146,9 @@ module Bunny
|
|
146
146
|
class InvalidCommand < ConnectionLevelException
|
147
147
|
end
|
148
148
|
|
149
|
+
class FrameError < ConnectionLevelException
|
150
|
+
end
|
151
|
+
|
149
152
|
class UnexpectedFrame < ConnectionLevelException
|
150
153
|
end
|
151
154
|
|
@@ -9,8 +9,9 @@ module Bunny
|
|
9
9
|
# API
|
10
10
|
#
|
11
11
|
|
12
|
-
def initialize(transport)
|
12
|
+
def initialize(transport, logger)
|
13
13
|
@transport = transport
|
14
|
+
@logger = logger
|
14
15
|
@mutex = Mutex.new
|
15
16
|
|
16
17
|
@last_activity_time = Time.now
|
@@ -46,10 +47,10 @@ module Bunny
|
|
46
47
|
sleep @interval
|
47
48
|
end
|
48
49
|
rescue IOError => ioe
|
49
|
-
|
50
|
+
@logger.error "I/O error in the hearbeat sender: #{ioe.message}"
|
50
51
|
stop
|
51
52
|
rescue Exception => e
|
52
|
-
|
53
|
+
@logger.error "I/O error in the hearbeat sender: #{ioe.message}"
|
53
54
|
stop
|
54
55
|
end
|
55
56
|
end
|
@@ -58,7 +59,8 @@ module Bunny
|
|
58
59
|
now = Time.now
|
59
60
|
|
60
61
|
if now > (@last_activity_time + @interval)
|
61
|
-
@
|
62
|
+
@logger.debug "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}"
|
63
|
+
@transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode)
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
data/lib/bunny/queue.rb
CHANGED
@@ -326,12 +326,14 @@ module Bunny
|
|
326
326
|
@channel.deregister_queue_named(old_name)
|
327
327
|
end
|
328
328
|
|
329
|
+
# TODO: inject and use logger
|
329
330
|
# puts "Recovering queue #{@name}"
|
330
331
|
begin
|
331
332
|
declare!
|
332
333
|
|
333
334
|
@channel.register_queue(self)
|
334
335
|
rescue Exception => e
|
336
|
+
# TODO: inject and use logger
|
335
337
|
puts "Caught #{e.inspect} while redeclaring and registering #{@name}!"
|
336
338
|
end
|
337
339
|
recover_bindings
|
@@ -340,6 +342,7 @@ module Bunny
|
|
340
342
|
# @private
|
341
343
|
def recover_bindings
|
342
344
|
@bindings.each do |b|
|
345
|
+
# TODO: inject and use logger
|
343
346
|
# puts "Recovering binding #{b.inspect}"
|
344
347
|
self.bind(b[:exchange], b)
|
345
348
|
end
|
@@ -6,12 +6,13 @@ module Bunny
|
|
6
6
|
# This loop uses a separate thread internally.
|
7
7
|
#
|
8
8
|
# This mimics the way RabbitMQ Java is designed quite closely.
|
9
|
-
class
|
9
|
+
class ReaderLoop
|
10
10
|
|
11
11
|
def initialize(transport, session, session_thread)
|
12
12
|
@transport = transport
|
13
13
|
@session = session
|
14
14
|
@session_thread = session_thread
|
15
|
+
@logger = @session.logger
|
15
16
|
end
|
16
17
|
|
17
18
|
|
@@ -33,7 +34,6 @@ module Bunny
|
|
33
34
|
rescue Errno::EBADF => ebadf
|
34
35
|
# ignored, happens when we loop after the transport has already been closed
|
35
36
|
rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError => e
|
36
|
-
puts "Exception in the main loop:"
|
37
37
|
log_exception(e)
|
38
38
|
|
39
39
|
@network_is_down = true
|
@@ -44,7 +44,6 @@ module Bunny
|
|
44
44
|
@session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
45
45
|
end
|
46
46
|
rescue Exception => e
|
47
|
-
puts "Unepxected exception in the main loop:"
|
48
47
|
log_exception(e)
|
49
48
|
|
50
49
|
@network_is_down = true
|
@@ -55,8 +54,6 @@ module Bunny
|
|
55
54
|
|
56
55
|
def run_once
|
57
56
|
frame = @transport.read_next_frame
|
58
|
-
@session.signal_activity!
|
59
|
-
|
60
57
|
return if frame.is_a?(AMQ::Protocol::HeartbeatFrame)
|
61
58
|
|
62
59
|
if !frame.final? || frame.method_class.has_content?
|
@@ -88,10 +85,10 @@ module Bunny
|
|
88
85
|
end
|
89
86
|
|
90
87
|
def log_exception(e)
|
91
|
-
|
92
|
-
|
88
|
+
@logger.error "Exception in the reader loop: #{e.class.name}: #{e.message}"
|
89
|
+
@logger.error "Backtrace: "
|
93
90
|
e.backtrace.each do |line|
|
94
|
-
|
91
|
+
@logger.error "\t#{line}"
|
95
92
|
end
|
96
93
|
end
|
97
94
|
end
|
data/lib/bunny/session.rb
CHANGED
@@ -4,7 +4,7 @@ require "thread"
|
|
4
4
|
require "bunny/transport"
|
5
5
|
require "bunny/channel_id_allocator"
|
6
6
|
require "bunny/heartbeat_sender"
|
7
|
-
require "bunny/
|
7
|
+
require "bunny/reader_loop"
|
8
8
|
require "bunny/authentication/credentials_encoder"
|
9
9
|
require "bunny/authentication/plain_mechanism_encoder"
|
10
10
|
require "bunny/authentication/external_mechanism_encoder"
|
@@ -70,6 +70,10 @@ module Bunny
|
|
70
70
|
# Authentication mechanism, e.g. "PLAIN" or "EXTERNAL"
|
71
71
|
# @return [String]
|
72
72
|
attr_reader :mechanism
|
73
|
+
# @return [Logger]
|
74
|
+
attr_reader :logger
|
75
|
+
# @return [Integer] Timeout for blocking protocol operations (queue.declare, queue.bind, etc), in milliseconds. Default is 4000.
|
76
|
+
attr_reader :continuation_timeout
|
73
77
|
|
74
78
|
|
75
79
|
# @param [String, Hash] connection_string_or_opts Connection string or a hash of connection options
|
@@ -103,10 +107,11 @@ module Bunny
|
|
103
107
|
@user = self.username_from(opts)
|
104
108
|
@pass = self.password_from(opts)
|
105
109
|
@vhost = self.vhost_from(opts)
|
106
|
-
@logfile = opts[:logfile]
|
107
|
-
@logging = opts[:logging] || false
|
110
|
+
@logfile = opts[:log_file] || opts[:logfile] || STDOUT
|
108
111
|
@threaded = opts.fetch(:threaded, true)
|
109
112
|
|
113
|
+
self.init_logger(opts[:log_level] || ENV["BUNNY_LOG_LEVEL"] || Logger::WARN)
|
114
|
+
|
110
115
|
# should automatic recovery from network failures be used?
|
111
116
|
@automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil?
|
112
117
|
true
|
@@ -114,6 +119,8 @@ module Bunny
|
|
114
119
|
opts[:automatically_recover] || opts[:automatic_recovery]
|
115
120
|
end
|
116
121
|
@network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
|
122
|
+
# in ms
|
123
|
+
@continuation_timeout = opts.fetch(:continuation_timeout, 4000)
|
117
124
|
|
118
125
|
@status = :not_connected
|
119
126
|
|
@@ -128,7 +135,6 @@ module Bunny
|
|
128
135
|
@locale = @opts.fetch(:locale, DEFAULT_LOCALE)
|
129
136
|
# mutex for the channel id => channel hash
|
130
137
|
@channel_mutex = Mutex.new
|
131
|
-
@network_mutex = Mutex.new
|
132
138
|
@channels = Hash.new
|
133
139
|
|
134
140
|
self.reset_continuations
|
@@ -180,8 +186,8 @@ module Bunny
|
|
180
186
|
self.init_connection
|
181
187
|
self.open_connection
|
182
188
|
|
183
|
-
@
|
184
|
-
self.
|
189
|
+
@reader_loop = nil
|
190
|
+
self.start_reader_loop if @threaded
|
185
191
|
|
186
192
|
@default_channel = self.create_channel
|
187
193
|
rescue Exception => e
|
@@ -201,11 +207,11 @@ module Bunny
|
|
201
207
|
# opened (this operation is very fast and inexpensive).
|
202
208
|
#
|
203
209
|
# @return [Bunny::Channel] Newly opened channel
|
204
|
-
def create_channel(n = nil)
|
210
|
+
def create_channel(n = nil, consumer_pool_size = 1)
|
205
211
|
if n && (ch = @channels[n])
|
206
212
|
ch
|
207
213
|
else
|
208
|
-
ch = Bunny::Channel.new(self, n)
|
214
|
+
ch = Bunny::Channel.new(self, n, ConsumerWorkPool.new(consumer_pool_size || 1))
|
209
215
|
ch.open
|
210
216
|
ch
|
211
217
|
end
|
@@ -341,7 +347,7 @@ module Bunny
|
|
341
347
|
|
342
348
|
# @private
|
343
349
|
def handle_frame(ch_number, method)
|
344
|
-
|
350
|
+
@logger.debug "Session#handle_frame on #{ch_number}: #{method.inspect}"
|
345
351
|
case method
|
346
352
|
when AMQ::Protocol::Channel::OpenOk then
|
347
353
|
@continuations.push(method)
|
@@ -357,14 +363,14 @@ module Bunny
|
|
357
363
|
begin
|
358
364
|
@continuations.clear
|
359
365
|
|
360
|
-
|
361
|
-
@
|
366
|
+
reader_loop.stop
|
367
|
+
@reader_loop = nil
|
362
368
|
|
363
369
|
@transport.close
|
364
370
|
rescue StandardError => e
|
365
|
-
|
366
|
-
|
367
|
-
|
371
|
+
@logger.error e.class.name
|
372
|
+
@logger.error e.message
|
373
|
+
@logger.error e.backtrace
|
368
374
|
ensure
|
369
375
|
@continuations.push(:__unblock__)
|
370
376
|
end
|
@@ -381,7 +387,7 @@ module Bunny
|
|
381
387
|
if ch = @channels[ch_number]
|
382
388
|
ch.handle_method(method)
|
383
389
|
else
|
384
|
-
#
|
390
|
+
@logger.warn "Channel #{ch_number} is not open on this connection!"
|
385
391
|
end
|
386
392
|
end
|
387
393
|
end
|
@@ -415,7 +421,7 @@ module Bunny
|
|
415
421
|
if !recovering_from_network_failure?
|
416
422
|
@recovering_from_network_failure = true
|
417
423
|
if recoverable_network_failure?(exception)
|
418
|
-
|
424
|
+
@logger.warn "Recovering from a network failure..."
|
419
425
|
@channels.each do |n, ch|
|
420
426
|
ch.maybe_kill_consumer_work_pool!
|
421
427
|
end
|
@@ -443,7 +449,7 @@ module Bunny
|
|
443
449
|
def recover_from_network_failure
|
444
450
|
begin
|
445
451
|
sleep @network_recovery_interval
|
446
|
-
|
452
|
+
@logger.debug "About to start connection recovery..."
|
447
453
|
start
|
448
454
|
|
449
455
|
if open?
|
@@ -452,7 +458,7 @@ module Bunny
|
|
452
458
|
recover_channels
|
453
459
|
end
|
454
460
|
rescue TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e
|
455
|
-
|
461
|
+
@logger.warn "TCP connection failed, reconnecting in 5 seconds"
|
456
462
|
sleep @network_recovery_interval
|
457
463
|
retry if recoverable_network_failure?(e)
|
458
464
|
end
|
@@ -470,11 +476,6 @@ module Bunny
|
|
470
476
|
end
|
471
477
|
end
|
472
478
|
|
473
|
-
# @private
|
474
|
-
def send_raw(data)
|
475
|
-
@transport.write(data)
|
476
|
-
end
|
477
|
-
|
478
479
|
# @private
|
479
480
|
def instantiate_connection_level_exception(frame)
|
480
481
|
case frame
|
@@ -482,6 +483,8 @@ module Bunny
|
|
482
483
|
klass = case frame.reply_code
|
483
484
|
when 320 then
|
484
485
|
ConnectionForced
|
486
|
+
when 501 then
|
487
|
+
FrameError
|
485
488
|
when 503 then
|
486
489
|
InvalidCommand
|
487
490
|
when 504 then
|
@@ -560,18 +563,18 @@ module Bunny
|
|
560
563
|
end
|
561
564
|
|
562
565
|
# @private
|
563
|
-
def
|
564
|
-
|
566
|
+
def start_reader_loop
|
567
|
+
reader_loop.start
|
565
568
|
end
|
566
569
|
|
567
570
|
# @private
|
568
|
-
def
|
569
|
-
@
|
571
|
+
def reader_loop
|
572
|
+
@reader_loop ||= ReaderLoop.new(@transport, self, Thread.current)
|
570
573
|
end
|
571
574
|
|
572
575
|
# @private
|
573
|
-
def
|
574
|
-
@
|
576
|
+
def maybe_shutdown_reader_loop
|
577
|
+
@reader_loop.stop if @reader_loop
|
575
578
|
end
|
576
579
|
|
577
580
|
# @private
|
@@ -585,11 +588,12 @@ module Bunny
|
|
585
588
|
#
|
586
589
|
# @raise [ConnectionClosedError]
|
587
590
|
# @private
|
588
|
-
def send_frame(frame)
|
591
|
+
def send_frame(frame, signal_activity = true)
|
589
592
|
if closed?
|
590
593
|
raise ConnectionClosedError.new(frame)
|
591
594
|
else
|
592
|
-
@
|
595
|
+
@transport.write(frame.encode)
|
596
|
+
signal_activity! if signal_activity
|
593
597
|
end
|
594
598
|
end
|
595
599
|
|
@@ -599,11 +603,12 @@ module Bunny
|
|
599
603
|
#
|
600
604
|
# @raise [ConnectionClosedError]
|
601
605
|
# @private
|
602
|
-
def send_frame_without_timeout(frame)
|
606
|
+
def send_frame_without_timeout(frame, signal_activity = true)
|
603
607
|
if closed?
|
604
608
|
raise ConnectionClosedError.new(frame)
|
605
609
|
else
|
606
|
-
@
|
610
|
+
@transport.write_without_timeout(frame.encode)
|
611
|
+
signal_activity! if signal_activity
|
607
612
|
end
|
608
613
|
end
|
609
614
|
|
@@ -618,8 +623,8 @@ module Bunny
|
|
618
623
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
619
624
|
# locking. Note that "single frame" methods do not need this kind of synchronization. MK.
|
620
625
|
channel.synchronize do
|
621
|
-
frames.each { |frame| self.send_frame(frame) }
|
622
|
-
|
626
|
+
frames.each { |frame| self.send_frame(frame, false) }
|
627
|
+
signal_activity!
|
623
628
|
end
|
624
629
|
end # send_frameset(frames)
|
625
630
|
|
@@ -635,10 +640,17 @@ module Bunny
|
|
635
640
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
636
641
|
# locking. Note that "single frame" methods do not need this kind of synchronization. MK.
|
637
642
|
channel.synchronize do
|
638
|
-
frames.each { |frame| self.send_frame_without_timeout(frame) }
|
643
|
+
frames.each { |frame| self.send_frame_without_timeout(frame, false) }
|
644
|
+
signal_activity!
|
639
645
|
end
|
640
646
|
end # send_frameset_without_timeout(frames)
|
641
647
|
|
648
|
+
# @return [String]
|
649
|
+
# @api public
|
650
|
+
def to_s
|
651
|
+
"Bunny::Session #{@user}@#{@host}:#{@port}, vhost=#{@vhost}"
|
652
|
+
end
|
653
|
+
|
642
654
|
protected
|
643
655
|
|
644
656
|
# @api private
|
@@ -659,6 +671,7 @@ module Bunny
|
|
659
671
|
# @api private
|
660
672
|
def open_connection
|
661
673
|
@transport.send_frame(AMQ::Protocol::Connection::StartOk.encode(@client_properties, @mechanism, self.encode_credentials(username, password), @locale))
|
674
|
+
@logger.debug "Sent connection.start-ok"
|
662
675
|
|
663
676
|
frame = begin
|
664
677
|
@transport.read_next_frame
|
@@ -669,6 +682,7 @@ module Bunny
|
|
669
682
|
end
|
670
683
|
if frame.nil?
|
671
684
|
@state = :closed
|
685
|
+
@logger.error "RabbitMQ closed TCP connection before AMQP 0.9.1 connection was finalized. Most likely this means authentication failure."
|
672
686
|
raise Bunny::PossibleAuthenticationFailureError.new(self.user, self.vhost, self.password.size)
|
673
687
|
end
|
674
688
|
|
@@ -682,11 +696,15 @@ module Bunny
|
|
682
696
|
else
|
683
697
|
negotiate_value(@client_heartbeat, connection_tune.heartbeat)
|
684
698
|
end
|
699
|
+
@logger.debug "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}"
|
700
|
+
@logger.info "Heartbeat interval used (in seconds): #{@heartbeat}"
|
685
701
|
|
686
702
|
@channel_id_allocator = ChannelIdAllocator.new(@channel_max)
|
687
703
|
|
688
704
|
@transport.send_frame(AMQ::Protocol::Connection::TuneOk.encode(@channel_max, @frame_max, @heartbeat))
|
705
|
+
@logger.debug "Sent connection.tune-ok with heartbeat interval = #{@heartbeat}, frame_max = #{@frame_max}, channel_max = #{@channel_max}"
|
689
706
|
@transport.send_frame(AMQ::Protocol::Connection::Open.encode(self.vhost))
|
707
|
+
@logger.debug "Sent connection.open with vhost = #{self.vhost}"
|
690
708
|
|
691
709
|
frame2 = begin
|
692
710
|
@transport.read_next_frame
|
@@ -697,6 +715,7 @@ module Bunny
|
|
697
715
|
end
|
698
716
|
if frame2.nil?
|
699
717
|
@state = :closed
|
718
|
+
@logger.warn "RabbitMQ closed TCP connection before AMQP 0.9.1 connection was finalized. Most likely this means authentication failure."
|
700
719
|
raise Bunny::PossibleAuthenticationFailureError.new(self.user, self.vhost, self.password.size)
|
701
720
|
end
|
702
721
|
connection_open_ok = frame2.decode_payload
|
@@ -726,8 +745,8 @@ module Bunny
|
|
726
745
|
|
727
746
|
# @api private
|
728
747
|
def initialize_heartbeat_sender
|
729
|
-
|
730
|
-
@heartbeat_sender = HeartbeatSender.new(@transport)
|
748
|
+
@logger.debug "Initializing heartbeat sender..."
|
749
|
+
@heartbeat_sender = HeartbeatSender.new(@transport, @logger)
|
731
750
|
@heartbeat_sender.start(@heartbeat)
|
732
751
|
end
|
733
752
|
|
@@ -749,7 +768,8 @@ module Bunny
|
|
749
768
|
# Sends AMQ protocol header (also known as preamble).
|
750
769
|
# @api private
|
751
770
|
def send_preamble
|
752
|
-
@transport.
|
771
|
+
@transport.write(AMQ::Protocol::PREAMBLE)
|
772
|
+
@logger.debug "Sent protocol preamble"
|
753
773
|
end
|
754
774
|
|
755
775
|
|
@@ -763,22 +783,47 @@ module Bunny
|
|
763
783
|
Authentication::CredentialsEncoder.for_session(self)
|
764
784
|
end
|
765
785
|
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
786
|
+
if defined?(JRUBY_VERSION)
|
787
|
+
# @api private
|
788
|
+
def reset_continuations
|
789
|
+
@continuations = Concurrent::LinkedContinuationQueue.new
|
790
|
+
end
|
791
|
+
else
|
792
|
+
# @api private
|
793
|
+
def reset_continuations
|
794
|
+
@continuations = Concurrent::ContinuationQueue.new
|
795
|
+
end
|
773
796
|
end
|
774
797
|
|
775
798
|
# @api private
|
776
799
|
def wait_on_continuations
|
777
800
|
unless @threaded
|
778
|
-
|
801
|
+
reader_loop.run_once until @continuations.length > 0
|
779
802
|
end
|
780
803
|
|
781
|
-
@continuations.
|
804
|
+
@continuations.poll(@continuation_timeout)
|
805
|
+
end
|
806
|
+
|
807
|
+
# @api private
|
808
|
+
def init_logger(level)
|
809
|
+
@logger = ::Logger.new(@logfile)
|
810
|
+
@logger.level = normalize_log_level(level)
|
811
|
+
@logger.progname = self.to_s
|
812
|
+
|
813
|
+
@logger
|
814
|
+
end
|
815
|
+
|
816
|
+
# @api private
|
817
|
+
def normalize_log_level(level)
|
818
|
+
case level
|
819
|
+
when :debug, Logger::DEBUG, "debug" then Logger::DEBUG
|
820
|
+
when :info, Logger::INFO, "info" then Logger::INFO
|
821
|
+
when :warn, Logger::WARN, "warn" then Logger::WARN
|
822
|
+
when :error, Logger::ERROR, "error" then Logger::ERROR
|
823
|
+
when :fatal, Logger::FATAL, "fatal" then Logger::FATAL
|
824
|
+
else
|
825
|
+
Logger::WARN
|
826
|
+
end
|
782
827
|
end
|
783
828
|
end # Session
|
784
829
|
|