bunny 0.8.0 → 0.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -1
- data/.travis.yml +14 -4
- data/ChangeLog.md +72 -0
- data/Gemfile +17 -11
- data/README.md +82 -0
- data/bunny.gemspec +6 -13
- data/examples/connection/heartbeat.rb +17 -0
- data/lib/bunny.rb +40 -56
- data/lib/bunny/channel.rb +615 -19
- data/lib/bunny/channel_id_allocator.rb +59 -0
- data/lib/bunny/compatibility.rb +24 -0
- data/lib/bunny/concurrent/condition.rb +63 -0
- data/lib/bunny/consumer.rb +42 -26
- data/lib/bunny/consumer_tag_generator.rb +22 -0
- data/lib/bunny/consumer_work_pool.rb +67 -0
- data/lib/bunny/exceptions.rb +128 -0
- data/lib/bunny/exchange.rb +131 -136
- data/lib/bunny/framing.rb +53 -0
- data/lib/bunny/heartbeat_sender.rb +59 -0
- data/lib/bunny/main_loop.rb +70 -0
- data/lib/bunny/message_metadata.rb +126 -0
- data/lib/bunny/queue.rb +102 -275
- data/lib/bunny/session.rb +478 -0
- data/lib/bunny/socket.rb +44 -0
- data/lib/bunny/system_timer.rb +9 -9
- data/lib/bunny/transport.rb +179 -0
- data/lib/bunny/version.rb +1 -1
- data/spec/compatibility/queue_declare_spec.rb +40 -0
- data/spec/higher_level_api/integration/basic_ack_spec.rb +54 -0
- data/spec/higher_level_api/integration/basic_consume_spec.rb +51 -0
- data/spec/higher_level_api/integration/basic_get_spec.rb +47 -0
- data/spec/higher_level_api/integration/basic_nack_spec.rb +39 -0
- data/spec/higher_level_api/integration/basic_publish_spec.rb +105 -0
- data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -0
- data/spec/higher_level_api/integration/basic_recover_spec.rb +18 -0
- data/spec/higher_level_api/integration/basic_reject_spec.rb +53 -0
- data/spec/higher_level_api/integration/basic_return_spec.rb +33 -0
- data/spec/higher_level_api/integration/channel_close_spec.rb +29 -0
- data/spec/higher_level_api/integration/channel_flow_spec.rb +24 -0
- data/spec/higher_level_api/integration/channel_open_spec.rb +57 -0
- data/spec/higher_level_api/integration/channel_open_stress_spec.rb +22 -0
- data/spec/higher_level_api/integration/confirm_select_spec.rb +19 -0
- data/spec/higher_level_api/integration/connection_spec.rb +340 -0
- data/spec/higher_level_api/integration/exchange_bind_spec.rb +31 -0
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +183 -0
- data/spec/higher_level_api/integration/exchange_delete_spec.rb +37 -0
- data/spec/higher_level_api/integration/exchange_unbind_spec.rb +40 -0
- data/spec/higher_level_api/integration/queue_bind_spec.rb +109 -0
- data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -0
- data/spec/higher_level_api/integration/queue_delete_spec.rb +38 -0
- data/spec/higher_level_api/integration/queue_purge_spec.rb +30 -0
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +33 -0
- data/spec/higher_level_api/integration/tx_commit_spec.rb +21 -0
- data/spec/higher_level_api/integration/tx_rollback_spec.rb +21 -0
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +57 -0
- data/spec/lower_level_api/integration/basic_consume_spec.rb +100 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/unit/bunny_spec.rb +15 -0
- data/spec/unit/concurrent/condition_spec.rb +66 -0
- metadata +135 -93
- data/CHANGELOG +0 -21
- data/README.textile +0 -76
- data/Rakefile +0 -14
- data/examples/simple.rb +0 -32
- data/examples/simple_ack.rb +0 -35
- data/examples/simple_consumer.rb +0 -55
- data/examples/simple_fanout.rb +0 -41
- data/examples/simple_headers.rb +0 -42
- data/examples/simple_publisher.rb +0 -29
- data/examples/simple_topic.rb +0 -61
- data/ext/amqp-0.9.1.json +0 -389
- data/ext/config.yml +0 -4
- data/ext/qparser.rb +0 -426
- data/lib/bunny/client.rb +0 -370
- data/lib/bunny/subscription.rb +0 -92
- data/lib/qrack/amq-client-url.rb +0 -165
- data/lib/qrack/channel.rb +0 -20
- data/lib/qrack/client.rb +0 -247
- data/lib/qrack/errors.rb +0 -5
- data/lib/qrack/protocol/protocol.rb +0 -135
- data/lib/qrack/protocol/spec.rb +0 -525
- data/lib/qrack/qrack.rb +0 -20
- data/lib/qrack/queue.rb +0 -40
- data/lib/qrack/subscription.rb +0 -152
- data/lib/qrack/transport/buffer.rb +0 -305
- data/lib/qrack/transport/frame.rb +0 -102
- data/spec/spec_09/amqp_url_spec.rb +0 -19
- data/spec/spec_09/bunny_spec.rb +0 -76
- data/spec/spec_09/connection_spec.rb +0 -34
- data/spec/spec_09/exchange_spec.rb +0 -173
- data/spec/spec_09/queue_spec.rb +0 -240
data/lib/bunny/exchange.rb
CHANGED
@@ -1,166 +1,161 @@
|
|
1
|
-
|
1
|
+
require "bunny/compatibility"
|
2
2
|
|
3
3
|
module Bunny
|
4
|
-
|
5
|
-
# *Exchanges* are the routing and distribution hub of AMQP. All messages that Bunny sends
|
6
|
-
# to an AMQP broker/server @have_to pass through an exchange in order to be routed to a
|
7
|
-
# destination queue. The AMQP specification defines the types of exchange that you can create.
|
8
|
-
#
|
9
|
-
# At the time of writing there are four (4) types of exchange defined:
|
10
|
-
#
|
11
|
-
# * @:direct@
|
12
|
-
# * @:fanout@
|
13
|
-
# * @:topic@
|
14
|
-
# * @:headers@
|
15
|
-
#
|
16
|
-
# AMQP-compliant brokers/servers are required to provide default exchanges for the @direct@ and
|
17
|
-
# @fanout@ exchange types. All default exchanges are prefixed with @'amq.'@, for example:
|
18
|
-
#
|
19
|
-
# * @amq.direct@
|
20
|
-
# * @amq.fanout@
|
21
|
-
# * @amq.topic@
|
22
|
-
# * @amq.match@ or @amq.headers@
|
23
|
-
#
|
24
|
-
# If you want more information about exchanges, please consult the documentation for your
|
25
|
-
# target broker/server or visit the "AMQP website":http://www.amqp.org to find the version of the
|
26
|
-
# specification that applies to your target broker/server.
|
27
4
|
class Exchange
|
28
5
|
|
29
|
-
|
30
|
-
|
31
|
-
def initialize(client, name, opts = {})
|
32
|
-
# check connection to server
|
33
|
-
raise Bunny::ConnectionError, 'Not connected to server' if client.status == :not_connected
|
6
|
+
include Bunny::Compatibility
|
34
7
|
|
35
|
-
@client, @name, @opts = client, name, opts
|
36
8
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
new_type = $1
|
41
|
-
# handle 'amq.match' default
|
42
|
-
new_type = 'headers' if new_type == 'match'
|
43
|
-
@type = new_type.to_sym
|
44
|
-
else
|
45
|
-
@type = opts[:type] || :direct
|
46
|
-
end
|
9
|
+
#
|
10
|
+
# API
|
11
|
+
#
|
47
12
|
|
48
|
-
|
49
|
-
|
13
|
+
# @return [Bunny::Channel]
|
14
|
+
attr_reader :channel
|
50
15
|
|
51
|
-
|
52
|
-
|
53
|
-
opts.delete(:nowait)
|
16
|
+
# @return [String]
|
17
|
+
attr_reader :name
|
54
18
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
:deprecated_ticket => 0, :deprecated_auto_delete => false, :deprecated_internal => false
|
59
|
-
}.merge(opts)
|
19
|
+
# Type of this exchange (one of: :direct, :fanout, :topic, :headers).
|
20
|
+
# @return [Symbol]
|
21
|
+
attr_reader :type
|
60
22
|
|
61
|
-
|
23
|
+
# @return [Symbol]
|
24
|
+
# @api plugin
|
25
|
+
attr_reader :status
|
62
26
|
|
63
|
-
|
27
|
+
# Options hash this exchange instance was instantiated with
|
28
|
+
# @return [Hash]
|
29
|
+
attr_accessor :opts
|
64
30
|
|
65
|
-
client.check_response(method, Qrack::Protocol::Exchange::DeclareOk, "Error declaring exchange #{name}: type = #{type}")
|
66
|
-
end
|
67
|
-
end
|
68
31
|
|
69
|
-
#
|
70
|
-
#
|
32
|
+
# The default exchange. Default exchange is a direct exchange that is predefined.
|
33
|
+
# It cannot be removed. Every queue is bind to this (direct) exchange by default with
|
34
|
+
# the following routing semantics: messages will be routed to the queue withe same
|
35
|
+
# same name as message's routing key. In other words, if a message is published with
|
36
|
+
# a routing key of "weather.usa.ca.sandiego" and there is a queue Q with this name,
|
37
|
+
# that message will be routed to Q.
|
71
38
|
#
|
72
|
-
# @
|
73
|
-
# If set to @true@, the server will only delete the exchange if it has no queue bindings. If the exchange has queue bindings the server does not delete it but raises a channel exception instead.
|
39
|
+
# @param [Bunny::Channel] channel Channel to use.
|
74
40
|
#
|
75
|
-
# @
|
76
|
-
#
|
41
|
+
# @example Publishing a messages to the tasks queue
|
42
|
+
# channel = Bunny::Channel.new(connection)
|
43
|
+
# tasks_queue = channel.queue("tasks")
|
44
|
+
# Bunny::Exchange.default(channel).publish("make clean", routing_key => "tasks")
|
77
45
|
#
|
78
|
-
# @
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
46
|
+
# @see Exchange
|
47
|
+
# @see http://files.travis-ci.org/docs/amqp/0.9.1/AMQP091Specification.pdf AMQP 0.9.1 specification (Section 2.1.2.4)
|
48
|
+
# @note Do not confuse default exchange with amq.direct: amq.direct is a pre-defined direct
|
49
|
+
# exchange that doesn't have any special routing semantics.
|
50
|
+
# @return [Exchange] An instance that corresponds to the default exchange (of type direct).
|
51
|
+
# @api public
|
52
|
+
def self.default(channel_or_connection)
|
53
|
+
self.new(channel_from(channel_or_connection), :direct, AMQ::Protocol::EMPTY_STRING, :no_declare => true)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def initialize(channel_or_connection, type, name, opts = {})
|
58
|
+
# old Bunny versions pass a connection here. In that case,
|
59
|
+
# we just use default channel from it. MK.
|
60
|
+
@channel = channel_from(channel_or_connection)
|
61
|
+
@name = name
|
62
|
+
@type = type
|
63
|
+
@options = self.class.add_default_options(name, opts)
|
83
64
|
|
84
|
-
|
65
|
+
@durable = @options[:durable]
|
66
|
+
@auto_delete = @options[:auto_delete]
|
67
|
+
@arguments = @options[:arguments]
|
85
68
|
|
86
|
-
|
69
|
+
declare! unless opts[:no_declare] || (@name =~ /^amq\..+/) || (@name == AMQ::Protocol::EMPTY_STRING)
|
87
70
|
|
88
|
-
|
71
|
+
@channel.register_exchange(self)
|
72
|
+
end
|
89
73
|
|
90
|
-
|
74
|
+
# @return [Boolean] true if this exchange was declared as durable (will survive broker restart).
|
75
|
+
# @api public
|
76
|
+
def durable?
|
77
|
+
@durable
|
78
|
+
end # durable?
|
91
79
|
|
92
|
-
|
80
|
+
# @return [Boolean] true if this exchange was declared as automatically deleted (deleted as soon as last consumer unbinds).
|
81
|
+
# @api public
|
82
|
+
def auto_delete?
|
83
|
+
@auto_delete
|
84
|
+
end # auto_delete?
|
93
85
|
|
94
|
-
|
95
|
-
|
86
|
+
def arguments
|
87
|
+
@arguments
|
96
88
|
end
|
97
89
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
#
|
108
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
90
|
+
|
91
|
+
|
92
|
+
def publish(payload, opts = {})
|
93
|
+
@channel.basic_publish(payload, self.name, opts.delete(:routing_key), opts)
|
94
|
+
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Deletes the exchange
|
100
|
+
# @api public
|
101
|
+
def delete(opts = {})
|
102
|
+
@channel.exchange_delete(@name, opts)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def bind(source, opts = {})
|
107
|
+
@channel.exchange_bind(source, self, opts)
|
108
|
+
end
|
109
|
+
|
110
|
+
def unbind(source, opts = {})
|
111
|
+
@channel.exchange_unbind(source, self, opts)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def on_return(&block)
|
116
|
+
@on_return = block
|
117
|
+
end
|
118
|
+
|
119
|
+
|
119
120
|
#
|
120
|
-
#
|
121
|
-
# Tells the server whether to persist the message. If set to @true@, the message will
|
122
|
-
# be persisted to disk and not lost if the server restarts. If set to @false@, the message
|
123
|
-
# will not be persisted across server restart. Setting to @true@ incurs a performance penalty
|
124
|
-
# as there is an extra cost associated with disk access.
|
121
|
+
# Implementation
|
125
122
|
#
|
126
|
-
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
routing_key = opts.delete(:key) || key
|
134
|
-
mandatory = opts.delete(:mandatory)
|
135
|
-
immediate = opts.delete(:immediate)
|
136
|
-
delivery_mode = opts.delete(:persistent) ? 2 : 1
|
137
|
-
content_type = opts.delete(:content_type) || 'application/octet-stream'
|
138
|
-
reply_to = opts.delete(:reply_to)
|
139
|
-
correlation_id = opts.delete(:correlation_id)
|
140
|
-
user_id = opts.delete(:user_id)
|
141
|
-
|
142
|
-
out << Qrack::Protocol::Basic::Publish.new({ :exchange => name,
|
143
|
-
:routing_key => routing_key,
|
144
|
-
:mandatory => mandatory,
|
145
|
-
:immediate => immediate,
|
146
|
-
:deprecated_ticket => 0 })
|
147
|
-
data = data.to_s
|
148
|
-
out << Qrack::Protocol::Header.new(
|
149
|
-
Qrack::Protocol::Basic,
|
150
|
-
data.bytesize, {
|
151
|
-
:content_type => content_type,
|
152
|
-
:delivery_mode => delivery_mode,
|
153
|
-
:reply_to => reply_to,
|
154
|
-
:correlation_id => correlation_id,
|
155
|
-
:user_id => user_id,
|
156
|
-
:priority => 0
|
157
|
-
}.merge(opts)
|
158
|
-
)
|
159
|
-
out << Qrack::Transport::Body.new(data)
|
160
|
-
|
161
|
-
client.send_frame(*out)
|
123
|
+
|
124
|
+
def handle_return(basic_return, properties, content)
|
125
|
+
if @on_return
|
126
|
+
@on_return.call(basic_return, properties, content)
|
127
|
+
else
|
128
|
+
# TODO: log a warning
|
129
|
+
end
|
162
130
|
end
|
163
131
|
|
164
|
-
|
132
|
+
protected
|
165
133
|
|
134
|
+
# @private
|
135
|
+
def declare!
|
136
|
+
@channel.exchange_declare(@name, @type, @options)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @private
|
140
|
+
def self.add_default_options(name, opts, block)
|
141
|
+
{ :exchange => name, :nowait => (block.nil? && !name.empty?) }.merge(opts)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @private
|
145
|
+
def self.add_default_options(name, opts)
|
146
|
+
# :nowait is always false for Bunny
|
147
|
+
h = { :queue => name, :nowait => false }.merge(opts)
|
148
|
+
|
149
|
+
if name.empty?
|
150
|
+
{
|
151
|
+
:passive => false,
|
152
|
+
:durable => false,
|
153
|
+
:auto_delete => false,
|
154
|
+
:arguments => nil
|
155
|
+
}.merge(h)
|
156
|
+
else
|
157
|
+
h
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
166
161
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Bunny
|
2
|
+
module Framing
|
3
|
+
ENCODINGS_SUPPORTED = defined? Encoding
|
4
|
+
HEADER_SLICE = (0..6).freeze
|
5
|
+
DATA_SLICE = (7..-1).freeze
|
6
|
+
PAYLOAD_SLICE = (0..-2).freeze
|
7
|
+
|
8
|
+
module String
|
9
|
+
class Frame < AMQ::Protocol::Frame
|
10
|
+
def self.decode(string)
|
11
|
+
header = string[HEADER_SLICE]
|
12
|
+
type, channel, size = self.decode_header(header)
|
13
|
+
data = string[DATA_SLICE]
|
14
|
+
payload = data[PAYLOAD_SLICE]
|
15
|
+
frame_end = data[-1, 1]
|
16
|
+
|
17
|
+
frame_end.force_encoding(AMQ::Protocol::Frame::FINAL_OCTET.encoding) if ENCODINGS_SUPPORTED
|
18
|
+
|
19
|
+
# 1) the size is miscalculated
|
20
|
+
if payload.bytesize != size
|
21
|
+
raise BadLengthError.new(size, payload.bytesize)
|
22
|
+
end
|
23
|
+
|
24
|
+
# 2) the size is OK, but the string doesn't end with FINAL_OCTET
|
25
|
+
raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
|
26
|
+
|
27
|
+
self.new(type, payload, channel)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end # String
|
31
|
+
|
32
|
+
|
33
|
+
module IO
|
34
|
+
class Frame < AMQ::Protocol::Frame
|
35
|
+
def self.decode(io)
|
36
|
+
header = io.read(7)
|
37
|
+
type, channel, size = self.decode_header(header)
|
38
|
+
data = io.read_fully(size + 1)
|
39
|
+
payload, frame_end = data[PAYLOAD_SLICE], data[-1, 1]
|
40
|
+
|
41
|
+
# 1) the size is miscalculated
|
42
|
+
if payload.bytesize != size
|
43
|
+
raise BadLengthError.new(size, payload.bytesize)
|
44
|
+
end
|
45
|
+
|
46
|
+
# 2) the size is OK, but the string doesn't end with FINAL_OCTET
|
47
|
+
raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
|
48
|
+
self.new(type, payload, channel)
|
49
|
+
end # self.from
|
50
|
+
end # Frame
|
51
|
+
end # IO
|
52
|
+
end # Framing
|
53
|
+
end # Bunny
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "amq/protocol/client"
|
3
|
+
require "amq/protocol/frame"
|
4
|
+
|
5
|
+
module Bunny
|
6
|
+
class HeartbeatSender
|
7
|
+
|
8
|
+
#
|
9
|
+
# API
|
10
|
+
#
|
11
|
+
|
12
|
+
def initialize(transport)
|
13
|
+
@transport = transport
|
14
|
+
@mutex = Mutex.new
|
15
|
+
|
16
|
+
@last_activity_time = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
def start(period = 30)
|
20
|
+
@mutex.synchronize do
|
21
|
+
@period = period
|
22
|
+
|
23
|
+
@thread = Thread.new(&method(:run))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
@mutex.synchronize { @thread.exit }
|
29
|
+
end
|
30
|
+
|
31
|
+
def signal_activity!
|
32
|
+
@last_activity_time = Time.now
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def run
|
38
|
+
begin
|
39
|
+
loop do
|
40
|
+
self.beat
|
41
|
+
|
42
|
+
sleep (@period / 2)
|
43
|
+
end
|
44
|
+
rescue IOError => ioe
|
45
|
+
# ignored
|
46
|
+
rescue Exception => e
|
47
|
+
puts e.message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def beat
|
52
|
+
now = Time.now
|
53
|
+
|
54
|
+
if now > (@last_activity_time + @period)
|
55
|
+
@transport.send_raw(AMQ::Protocol::HeartbeatFrame.encode)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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
|
+
class MainLoop
|
10
|
+
|
11
|
+
def initialize(transport, session)
|
12
|
+
@transport = transport
|
13
|
+
@session = session
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def start
|
18
|
+
@thread = Thread.new(&method(:run_loop))
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_loop
|
22
|
+
loop do
|
23
|
+
begin
|
24
|
+
break if @stopping
|
25
|
+
|
26
|
+
frame = @transport.read_next_frame
|
27
|
+
@session.signal_activity!
|
28
|
+
|
29
|
+
next if frame.is_a?(AMQ::Protocol::HeartbeatFrame)
|
30
|
+
|
31
|
+
if !frame.final? || frame.method_class.has_content?
|
32
|
+
header = @transport.read_next_frame
|
33
|
+
content = ''
|
34
|
+
|
35
|
+
if header.body_size > 0
|
36
|
+
loop do
|
37
|
+
body_frame = @transport.read_next_frame
|
38
|
+
content << body_frame.decode_payload
|
39
|
+
|
40
|
+
break if content.bytesize >= header.body_size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@session.handle_frameset(frame.channel, [frame.decode_payload, header.decode_payload, content])
|
45
|
+
else
|
46
|
+
@session.handle_frame(frame.channel, frame.decode_payload)
|
47
|
+
end
|
48
|
+
rescue Timeout::Error => te
|
49
|
+
# given that the server may be pushing data to us, timeout detection/handling
|
50
|
+
# should happen per operation and not in this loop
|
51
|
+
rescue Errno::EBADF => ebadf
|
52
|
+
# ignored, happens when we loop after the transport has already been closed
|
53
|
+
rescue Exception => e
|
54
|
+
puts e.class.name
|
55
|
+
puts e.message
|
56
|
+
puts e.backtrace
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def stop
|
62
|
+
@stopping = true
|
63
|
+
end
|
64
|
+
|
65
|
+
def kill
|
66
|
+
@thread.kill
|
67
|
+
@thread.join
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|