garaio_bunny 2.19.1

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.
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