bunny 0.9.0.pre10 → 0.9.0.pre11
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 +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
|
|