garaio_bunny 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +231 -0
  3. data/lib/amq/protocol/extensions.rb +16 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +55 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
  7. data/lib/bunny/channel.rb +2055 -0
  8. data/lib/bunny/channel_id_allocator.rb +82 -0
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
  10. data/lib/bunny/concurrent/condition.rb +66 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +62 -0
  12. data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
  13. data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
  14. data/lib/bunny/consumer.rb +128 -0
  15. data/lib/bunny/consumer_tag_generator.rb +23 -0
  16. data/lib/bunny/consumer_work_pool.rb +122 -0
  17. data/lib/bunny/cruby/socket.rb +110 -0
  18. data/lib/bunny/cruby/ssl_socket.rb +118 -0
  19. data/lib/bunny/delivery_info.rb +93 -0
  20. data/lib/bunny/exceptions.rb +269 -0
  21. data/lib/bunny/exchange.rb +275 -0
  22. data/lib/bunny/framing.rb +56 -0
  23. data/lib/bunny/get_response.rb +83 -0
  24. data/lib/bunny/heartbeat_sender.rb +71 -0
  25. data/lib/bunny/jruby/socket.rb +57 -0
  26. data/lib/bunny/jruby/ssl_socket.rb +58 -0
  27. data/lib/bunny/message_properties.rb +119 -0
  28. data/lib/bunny/queue.rb +393 -0
  29. data/lib/bunny/reader_loop.rb +158 -0
  30. data/lib/bunny/return_info.rb +74 -0
  31. data/lib/bunny/session.rb +1483 -0
  32. data/lib/bunny/socket.rb +14 -0
  33. data/lib/bunny/ssl_socket.rb +14 -0
  34. data/lib/bunny/test_kit.rb +41 -0
  35. data/lib/bunny/timeout.rb +7 -0
  36. data/lib/bunny/transport.rb +526 -0
  37. data/lib/bunny/version.rb +6 -0
  38. data/lib/bunny/versioned_delivery_tag.rb +28 -0
  39. data/lib/bunny.rb +92 -0
  40. metadata +127 -0
@@ -0,0 +1,119 @@
1
+ module Bunny
2
+ # Wraps basic properties hash as returned by amq-protocol to
3
+ # provide access to the delivery properties as immutable hash as
4
+ # well as methods.
5
+ class MessageProperties
6
+
7
+ #
8
+ # Behaviors
9
+ #
10
+
11
+ include Enumerable
12
+
13
+ #
14
+ # API
15
+ #
16
+
17
+ # @private
18
+ def initialize(properties)
19
+ @properties = properties
20
+ end
21
+
22
+ # Iterates over the message properties
23
+ # @see Enumerable#each
24
+ def each(*args, &block)
25
+ @properties.each(*args, &block)
26
+ end
27
+
28
+ # Accesses message properties by key
29
+ # @see Hash#[]
30
+ def [](k)
31
+ @properties[k]
32
+ end
33
+
34
+ # @return [Hash] Hash representation of this delivery info
35
+ def to_hash
36
+ @properties
37
+ end
38
+
39
+ # @private
40
+ def to_s
41
+ to_hash.to_s
42
+ end
43
+
44
+ # @private
45
+ def inspect
46
+ to_hash.inspect
47
+ end
48
+
49
+ # @return [String] (Optional) content type of the message, as set by the publisher
50
+ def content_type
51
+ @properties[:content_type]
52
+ end
53
+
54
+ # @return [String] (Optional) content encoding of the message, as set by the publisher
55
+ def content_encoding
56
+ @properties[:content_encoding]
57
+ end
58
+
59
+ # @return [String] Message headers
60
+ def headers
61
+ @properties[:headers]
62
+ end
63
+
64
+ # @return [Integer] Delivery mode (persistent or transient)
65
+ def delivery_mode
66
+ @properties[:delivery_mode]
67
+ end
68
+
69
+ # @return [Integer] Message priority, as set by the publisher
70
+ def priority
71
+ @properties[:priority]
72
+ end
73
+
74
+ # @return [String] What message this message is a reply to (or corresponds to), as set by the publisher
75
+ def correlation_id
76
+ @properties[:correlation_id]
77
+ end
78
+
79
+ # @return [String] (Optional) How to reply to the publisher (usually a reply queue name)
80
+ def reply_to
81
+ @properties[:reply_to]
82
+ end
83
+
84
+ # @return [String] Message expiration, as set by the publisher
85
+ def expiration
86
+ @properties[:expiration]
87
+ end
88
+
89
+ # @return [String] Message ID, as set by the publisher
90
+ def message_id
91
+ @properties[:message_id]
92
+ end
93
+
94
+ # @return [Time] Message timestamp, as set by the publisher
95
+ def timestamp
96
+ @properties[:timestamp]
97
+ end
98
+
99
+ # @return [String] Message type, as set by the publisher
100
+ def type
101
+ @properties[:type]
102
+ end
103
+
104
+ # @return [String] Publishing user, as set by the publisher
105
+ def user_id
106
+ @properties[:user_id]
107
+ end
108
+
109
+ # @return [String] Publishing application, as set by the publisher
110
+ def app_id
111
+ @properties[:app_id]
112
+ end
113
+
114
+ # @return [String] Cluster ID, as set by the publisher
115
+ def cluster_id
116
+ @properties[:cluster_id]
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,393 @@
1
+ require "bunny/get_response"
2
+
3
+ module Bunny
4
+ # Represents AMQP 0.9.1 queue.
5
+ #
6
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
7
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
8
+ class Queue
9
+
10
+ #
11
+ # API
12
+ #
13
+
14
+ # @return [Bunny::Channel] Channel this queue uses
15
+ attr_reader :channel
16
+ # @return [String] Queue name
17
+ attr_reader :name
18
+ # @return [Hash] Options this queue was created with
19
+ attr_reader :options
20
+
21
+ # @param [Bunny::Channel] channel Channel this queue will use.
22
+ # @param [String] name Queue name. Pass an empty string to make RabbitMQ generate a unique one.
23
+ # @param [Hash] opts Queue properties
24
+ #
25
+ # @option opts [Boolean] :durable (false) Should this queue be durable?
26
+ # @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects?
27
+ # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
28
+ # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
29
+ #
30
+ # @see Bunny::Channel#queue
31
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
32
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
33
+ # @api public
34
+ def initialize(channel, name = AMQ::Protocol::EMPTY_STRING, opts = {})
35
+ # old Bunny versions pass a connection here. In that case,
36
+ # we just use default channel from it. MK.
37
+ @channel = channel
38
+ @name = name
39
+ @options = self.class.add_default_options(name, opts)
40
+
41
+ @durable = @options[:durable]
42
+ @exclusive = @options[:exclusive]
43
+ @server_named = @name.empty?
44
+ @auto_delete = @options[:auto_delete]
45
+ @arguments = @options[:arguments]
46
+
47
+ @bindings = Array.new
48
+
49
+ @default_consumer = nil
50
+
51
+ declare! unless opts[:no_declare]
52
+
53
+ @channel.register_queue(self)
54
+ end
55
+
56
+ # @return [Boolean] true if this queue was declared as durable (will survive broker restart).
57
+ # @api public
58
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
59
+ def durable?
60
+ @durable
61
+ end # durable?
62
+
63
+ # @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer)
64
+ # @api public
65
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
66
+ def exclusive?
67
+ @exclusive
68
+ end # exclusive?
69
+
70
+ # @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds).
71
+ # @api public
72
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
73
+ def auto_delete?
74
+ @auto_delete
75
+ end # auto_delete?
76
+
77
+ # @return [Boolean] true if this queue was declared as server named.
78
+ # @api public
79
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
80
+ def server_named?
81
+ @server_named
82
+ end # server_named?
83
+
84
+ # @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins)
85
+ # @api public
86
+ def arguments
87
+ @arguments
88
+ end
89
+
90
+ def to_s
91
+ oid = ("0x%x" % (self.object_id << 1))
92
+ "<#{self.class.name}:#{oid} @name=\"#{name}\" channel=#{@channel.to_s} @durable=#{@durable} @auto_delete=#{@auto_delete} @exclusive=#{@exclusive} @arguments=#{@arguments}>"
93
+ end
94
+
95
+ def inspect
96
+ to_s
97
+ end
98
+
99
+ # Binds queue to an exchange
100
+ #
101
+ # @param [Bunny::Exchange,String] exchange Exchange to bind to
102
+ # @param [Hash] opts Binding properties
103
+ #
104
+ # @option opts [String] :routing_key Routing key
105
+ # @option opts [Hash] :arguments ({}) Additional optional binding arguments
106
+ #
107
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
108
+ # @see http://rubybunny.info/articles/bindings.html Bindings guide
109
+ # @api public
110
+ def bind(exchange, opts = {})
111
+ @channel.queue_bind(@name, exchange, opts)
112
+
113
+ exchange_name = if exchange.respond_to?(:name)
114
+ exchange.name
115
+ else
116
+ exchange
117
+ end
118
+
119
+
120
+ # store bindings for automatic recovery. We need to be very careful to
121
+ # not cause an infinite rebinding loop here when we recover. MK.
122
+ binding = { :exchange => exchange_name, :routing_key => (opts[:routing_key] || opts[:key]), :arguments => opts[:arguments] }
123
+ @bindings.push(binding) unless @bindings.include?(binding)
124
+
125
+ self
126
+ end
127
+
128
+ # Unbinds queue from an exchange
129
+ #
130
+ # @param [Bunny::Exchange,String] exchange Exchange to unbind from
131
+ # @param [Hash] opts Binding properties
132
+ #
133
+ # @option opts [String] :routing_key Routing key
134
+ # @option opts [Hash] :arguments ({}) Additional optional binding arguments
135
+ #
136
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
137
+ # @see http://rubybunny.info/articles/bindings.html Bindings guide
138
+ # @api public
139
+ def unbind(exchange, opts = {})
140
+ @channel.queue_unbind(@name, exchange, opts)
141
+
142
+ exchange_name = if exchange.respond_to?(:name)
143
+ exchange.name
144
+ else
145
+ exchange
146
+ end
147
+
148
+
149
+ @bindings.delete_if { |b| b[:exchange] == exchange_name && b[:routing_key] == (opts[:routing_key] || opts[:key]) && b[:arguments] == opts[:arguments] }
150
+
151
+ self
152
+ end
153
+
154
+ # Adds a consumer to the queue (subscribes for message deliveries).
155
+ #
156
+ # @param [Hash] opts Options
157
+ #
158
+ # @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead
159
+ # @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements?
160
+ # @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue?
161
+ # @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin)
162
+ # @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let Bunny generate it for you.
163
+ # @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions
164
+ #
165
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
166
+ # @api public
167
+ def subscribe(opts = {
168
+ :consumer_tag => @channel.generate_consumer_tag,
169
+ :manual_ack => false,
170
+ :exclusive => false,
171
+ :block => false,
172
+ :on_cancellation => nil
173
+ }, &block)
174
+
175
+ unless opts[:ack].nil?
176
+ warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
177
+ opts[:manual_ack] = opts[:ack]
178
+ end
179
+
180
+ ctag = opts.fetch(:consumer_tag, @channel.generate_consumer_tag)
181
+ consumer = Consumer.new(@channel,
182
+ self,
183
+ ctag,
184
+ !opts[:manual_ack],
185
+ opts[:exclusive],
186
+ opts[:arguments])
187
+
188
+ consumer.on_delivery(&block)
189
+ consumer.on_cancellation(&opts[:on_cancellation]) if opts[:on_cancellation]
190
+
191
+ @channel.basic_consume_with(consumer)
192
+ if opts[:block]
193
+ # joins current thread with the consumers pool, will block
194
+ # the current thread for as long as the consumer pool is active
195
+ @channel.work_pool.join
196
+ end
197
+
198
+ consumer
199
+ end
200
+
201
+ # Adds a consumer object to the queue (subscribes for message deliveries).
202
+ #
203
+ # @param [Bunny::Consumer] consumer a {Bunny::Consumer} subclass that implements consumer interface
204
+ # @param [Hash] opts Options
205
+ #
206
+ # @option opts [Boolean] block (false) Should the call block calling thread?
207
+ #
208
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
209
+ # @api public
210
+ def subscribe_with(consumer, opts = {:block => false})
211
+ @channel.basic_consume_with(consumer)
212
+
213
+ @channel.work_pool.join if opts[:block]
214
+ consumer
215
+ end
216
+
217
+ # @param [Hash] opts Options
218
+ #
219
+ # @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead
220
+ # @option opts [Boolean] :manual_ack (false) Will the message be acknowledged manually?
221
+ #
222
+ # @return [Array] Triple of delivery info, message properties and message content.
223
+ # If the queue is empty, all three will be nils.
224
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
225
+ # @see Bunny::Queue#subscribe
226
+ # @api public
227
+ #
228
+ # @example
229
+ # conn = Bunny.new
230
+ # conn.start
231
+ #
232
+ # ch = conn.create_channel
233
+ # q = ch.queue("test1")
234
+ # x = ch.default_exchange
235
+ # x.publish("Hello, everybody!", :routing_key => 'test1')
236
+ #
237
+ # delivery_info, properties, payload = q.pop
238
+ #
239
+ # puts "This is the message: " + payload + "\n\n"
240
+ # conn.close
241
+ def pop(opts = {:manual_ack => false}, &block)
242
+ unless opts[:ack].nil?
243
+ warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
244
+ opts[:manual_ack] = opts[:ack]
245
+ end
246
+
247
+ get_response, properties, content = @channel.basic_get(@name, opts)
248
+
249
+ if block
250
+ if properties
251
+ di = GetResponse.new(get_response, @channel)
252
+ mp = MessageProperties.new(properties)
253
+
254
+ block.call(di, mp, content)
255
+ else
256
+ block.call(nil, nil, nil)
257
+ end
258
+ else
259
+ if properties
260
+ di = GetResponse.new(get_response, @channel)
261
+ mp = MessageProperties.new(properties)
262
+ [di, mp, content]
263
+ else
264
+ [nil, nil, nil]
265
+ end
266
+ end
267
+ end
268
+ alias get pop
269
+
270
+ # Publishes a message to the queue via default exchange. Takes the same arguments
271
+ # as {Bunny::Exchange#publish}
272
+ #
273
+ # @see Bunny::Exchange#publish
274
+ # @see Bunny::Channel#default_exchange
275
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
276
+ def publish(payload, opts = {})
277
+ @channel.default_exchange.publish(payload, opts.merge(:routing_key => @name))
278
+
279
+ self
280
+ end
281
+
282
+
283
+ # Deletes the queue
284
+ #
285
+ # @param [Hash] opts Options
286
+ #
287
+ # @option opts [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers?
288
+ # @option opts [Boolean] if_empty (false) Should this queue be deleted only if it has no messages?
289
+ #
290
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
291
+ # @api public
292
+ def delete(opts = {})
293
+ @channel.deregister_queue(self)
294
+ @channel.queue_delete(@name, opts)
295
+ end
296
+
297
+ # Purges a queue (removes all messages from it)
298
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
299
+ # @api public
300
+ def purge(opts = {})
301
+ @channel.queue_purge(@name, opts)
302
+
303
+ self
304
+ end
305
+
306
+ # @return [Hash] A hash with information about the number of queue messages and consumers
307
+ # @see #message_count
308
+ # @see #consumer_count
309
+ def status
310
+ queue_declare_ok = @channel.queue_declare(@name, @options.merge(:passive => true))
311
+ {:message_count => queue_declare_ok.message_count,
312
+ :consumer_count => queue_declare_ok.consumer_count}
313
+ end
314
+
315
+ # @return [Integer] How many messages the queue has ready (e.g. not delivered but not unacknowledged)
316
+ def message_count
317
+ s = self.status
318
+ s[:message_count]
319
+ end
320
+
321
+ # @return [Integer] How many active consumers the queue has
322
+ def consumer_count
323
+ s = self.status
324
+ s[:consumer_count]
325
+ end
326
+
327
+ #
328
+ # Recovery
329
+ #
330
+
331
+ # @private
332
+ def recover_from_network_failure
333
+ if self.server_named?
334
+ old_name = @name.dup
335
+ @name = AMQ::Protocol::EMPTY_STRING
336
+
337
+ @channel.deregister_queue_named(old_name)
338
+ end
339
+
340
+ # TODO: inject and use logger
341
+ # puts "Recovering queue #{@name}"
342
+ begin
343
+ declare! unless @options[:no_declare]
344
+
345
+ @channel.register_queue(self)
346
+ rescue Exception => e
347
+ # TODO: inject and use logger
348
+ puts "Caught #{e.inspect} while redeclaring and registering #{@name}!"
349
+ end
350
+ recover_bindings
351
+ end
352
+
353
+ # @private
354
+ def recover_bindings
355
+ @bindings.each do |b|
356
+ # TODO: inject and use logger
357
+ # puts "Recovering binding #{b.inspect}"
358
+ self.bind(b[:exchange], b)
359
+ end
360
+ end
361
+
362
+
363
+ #
364
+ # Implementation
365
+ #
366
+
367
+ # @private
368
+ def declare!
369
+ queue_declare_ok = @channel.queue_declare(@name, @options)
370
+ @name = queue_declare_ok.queue
371
+ end
372
+
373
+ protected
374
+
375
+ # @private
376
+ def self.add_default_options(name, opts)
377
+ # :nowait is always false for Bunny
378
+ h = { :queue => name, :nowait => false }.merge(opts)
379
+
380
+ if name.empty?
381
+ {
382
+ :passive => false,
383
+ :durable => false,
384
+ :exclusive => false,
385
+ :auto_delete => false,
386
+ :arguments => nil
387
+ }.merge(h)
388
+ else
389
+ h
390
+ end
391
+ end
392
+ end
393
+ end
@@ -0,0 +1,158 @@
1
+ require "thread"
2
+
3
+ module Bunny
4
+ # Network activity loop that reads and passes incoming AMQP 0.9.1 methods for
5
+ # processing. They are dispatched further down the line in Bunny::Session and Bunny::Channel.
6
+ # This loop uses a separate thread internally.
7
+ #
8
+ # This mimics the way RabbitMQ Java is designed quite closely.
9
+ # @private
10
+ class ReaderLoop
11
+
12
+ def initialize(transport, session, session_error_handler)
13
+ @transport = transport
14
+ @session = session
15
+ @session_error_handler = session_error_handler
16
+ @logger = @session.logger
17
+
18
+ @mutex = Mutex.new
19
+
20
+ @stopping = false
21
+ @stopped = false
22
+ @network_is_down = false
23
+ end
24
+
25
+
26
+ def start
27
+ @thread = Thread.new(&method(:run_loop))
28
+ end
29
+
30
+ def resume
31
+ start
32
+ end
33
+
34
+
35
+ def run_loop
36
+ loop do
37
+ begin
38
+ break if @mutex.synchronize { @stopping || @stopped || @network_is_down }
39
+ run_once
40
+ rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Timeout::Error,
41
+ OpenSSL::OpenSSLError => e
42
+ break if terminate? || @session.closing? || @session.closed?
43
+
44
+ @network_is_down = true
45
+ if @session.automatically_recover?
46
+ log_exception(e, level: :warn)
47
+ @session.handle_network_failure(e)
48
+ else
49
+ log_exception(e)
50
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
51
+ end
52
+ rescue ShutdownSignal => _
53
+ @mutex.synchronize { @stopping = true }
54
+ break
55
+ rescue Exception => e
56
+ break if terminate?
57
+ if !(@session.closing? || @session.closed?)
58
+ log_exception(e)
59
+
60
+ @network_is_down = true
61
+ @session_error_handler.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
62
+ end
63
+ rescue Errno::EBADF => _ebadf
64
+ break if terminate?
65
+ # ignored, happens when we loop after the transport has already been closed
66
+ @mutex.synchronize { @stopping = true }
67
+ end
68
+ end
69
+
70
+ @mutex.synchronize { @stopped = true }
71
+ end
72
+
73
+ def run_once
74
+ frame = @transport.read_next_frame
75
+ return if frame.is_a?(AMQ::Protocol::HeartbeatFrame)
76
+
77
+ if !frame.final? || frame.method_class.has_content?
78
+ header = @transport.read_next_frame
79
+ content = ''
80
+
81
+ if header.body_size > 0
82
+ loop do
83
+ body_frame = @transport.read_next_frame
84
+ content << body_frame.decode_payload
85
+
86
+ break if content.bytesize >= header.body_size
87
+ end
88
+ end
89
+
90
+ @session.handle_frameset(frame.channel, [frame.decode_payload, header.decode_payload, content])
91
+ else
92
+ @session.handle_frame(frame.channel, frame.decode_payload)
93
+ end
94
+ end
95
+
96
+ def stop
97
+ @mutex.synchronize { @stopping = true }
98
+ end
99
+
100
+ def stopped?
101
+ @mutex.synchronize { @stopped }
102
+ end
103
+
104
+ def stopping?
105
+ @mutex.synchronize { @stopping }
106
+ end
107
+
108
+ def terminate_with(e)
109
+ @mutex.synchronize { @stopping = true }
110
+
111
+ self.raise(e)
112
+ end
113
+
114
+ def raise(e)
115
+ @thread.raise(e) if @thread
116
+ end
117
+
118
+ def join
119
+ # Thread#join can/would trigger a re-raise of an unhandled exception in this thread.
120
+ # In addition, Thread.handle_interrupt can be used by other libraries or application code
121
+ # that would make this join operation fail with an obscure exception.
122
+ # So we try to save everyone some really unpleasant debugging time by introducing
123
+ # this condition which typically would not evaluate to true anyway.
124
+ #
125
+ # See ruby-amqp/bunny#589 and ruby-amqp/bunny#590 for background.
126
+ @thread.join if @thread && @thread != Thread.current
127
+ end
128
+
129
+ def kill
130
+ if @thread
131
+ @thread.kill
132
+ @thread.join
133
+ end
134
+ end
135
+
136
+ protected
137
+
138
+ def log_exception(e, level: :error)
139
+ if !(io_error?(e) && (@session.closing? || @session.closed?))
140
+ @logger.send level, "Exception in the reader loop: #{e.class.name}: #{e.message}"
141
+ @logger.send level, "Backtrace: "
142
+ e.backtrace.each do |line|
143
+ @logger.send level, "\t#{line}"
144
+ end
145
+ end
146
+ end
147
+
148
+ def io_error?(e)
149
+ [AMQ::Protocol::EmptyResponseError, IOError, SystemCallError].any? do |klazz|
150
+ e.is_a?(klazz)
151
+ end
152
+ end
153
+
154
+ def terminate?
155
+ @mutex.synchronize { @stopping || @stopped }
156
+ end
157
+ end
158
+ end