garaio_bunny 2.19.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +231 -0
- data/lib/amq/protocol/extensions.rb +16 -0
- data/lib/bunny/authentication/credentials_encoder.rb +55 -0
- data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
- data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
- data/lib/bunny/channel.rb +2055 -0
- data/lib/bunny/channel_id_allocator.rb +82 -0
- data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
- data/lib/bunny/concurrent/condition.rb +66 -0
- data/lib/bunny/concurrent/continuation_queue.rb +62 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
- data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/bunny/consumer.rb +128 -0
- data/lib/bunny/consumer_tag_generator.rb +23 -0
- data/lib/bunny/consumer_work_pool.rb +122 -0
- data/lib/bunny/cruby/socket.rb +110 -0
- data/lib/bunny/cruby/ssl_socket.rb +118 -0
- data/lib/bunny/delivery_info.rb +93 -0
- data/lib/bunny/exceptions.rb +269 -0
- data/lib/bunny/exchange.rb +275 -0
- data/lib/bunny/framing.rb +56 -0
- data/lib/bunny/get_response.rb +83 -0
- data/lib/bunny/heartbeat_sender.rb +71 -0
- data/lib/bunny/jruby/socket.rb +57 -0
- data/lib/bunny/jruby/ssl_socket.rb +58 -0
- data/lib/bunny/message_properties.rb +119 -0
- data/lib/bunny/queue.rb +393 -0
- data/lib/bunny/reader_loop.rb +158 -0
- data/lib/bunny/return_info.rb +74 -0
- data/lib/bunny/session.rb +1483 -0
- data/lib/bunny/socket.rb +14 -0
- data/lib/bunny/ssl_socket.rb +14 -0
- data/lib/bunny/test_kit.rb +41 -0
- data/lib/bunny/timeout.rb +7 -0
- data/lib/bunny/transport.rb +526 -0
- data/lib/bunny/version.rb +6 -0
- data/lib/bunny/versioned_delivery_tag.rb +28 -0
- data/lib/bunny.rb +92 -0
- metadata +127 -0
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'amq/protocol'
|
2
|
+
|
3
|
+
module Bunny
|
4
|
+
# Represents AMQP 0.9.1 exchanges.
|
5
|
+
#
|
6
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
7
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
8
|
+
class Exchange
|
9
|
+
|
10
|
+
#
|
11
|
+
# API
|
12
|
+
#
|
13
|
+
|
14
|
+
# @return [Bunny::Channel]
|
15
|
+
attr_reader :channel
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
attr_reader :name
|
19
|
+
|
20
|
+
# Type of this exchange (one of: :direct, :fanout, :topic, :headers).
|
21
|
+
# @return [Symbol]
|
22
|
+
attr_reader :type
|
23
|
+
|
24
|
+
# @return [Symbol]
|
25
|
+
# @api plugin
|
26
|
+
attr_reader :status
|
27
|
+
|
28
|
+
# Options hash this exchange instance was instantiated with
|
29
|
+
# @return [Hash]
|
30
|
+
attr_accessor :opts
|
31
|
+
|
32
|
+
|
33
|
+
# The default exchange. This exchange is a direct exchange that is predefined by the broker
|
34
|
+
# and that cannot be removed. Every queue is bound to this exchange by default with
|
35
|
+
# the following routing semantics: messages will be routed to the queue with the same
|
36
|
+
# name as the message's routing key. In other words, if a message is published with
|
37
|
+
# a routing key of "weather.usa.ca.sandiego" and there is a queue with this name,
|
38
|
+
# the message will be routed to the queue.
|
39
|
+
#
|
40
|
+
# @param [Bunny::Channel] channel_or_connection Channel to use. {Bunny::Session} instances
|
41
|
+
# are only supported for backwards compatibility.
|
42
|
+
#
|
43
|
+
# @example Publishing a messages to the tasks queue
|
44
|
+
# channel = Bunny::Channel.new(connection)
|
45
|
+
# tasks_queue = channel.queue("tasks")
|
46
|
+
# Bunny::Exchange.default(channel).publish("make clean", :routing_key => "tasks")
|
47
|
+
#
|
48
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
49
|
+
# @see http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.1.2.4)
|
50
|
+
# @note Do not confuse the default exchange with amq.direct: amq.direct is a pre-defined direct
|
51
|
+
# exchange that doesn't have any special routing semantics.
|
52
|
+
# @return [Exchange] An instance that corresponds to the default exchange (of type direct).
|
53
|
+
# @api public
|
54
|
+
def self.default(channel_or_connection)
|
55
|
+
self.new(channel_or_connection, :direct, AMQ::Protocol::EMPTY_STRING, :no_declare => true)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Bunny::Channel] channel Channel this exchange will use.
|
59
|
+
# @param [Symbol,String] type Exchange type
|
60
|
+
# @param [String] name Exchange name
|
61
|
+
# @param [Hash] opts Exchange properties
|
62
|
+
#
|
63
|
+
# @option opts [Boolean] :durable (false) Should this exchange be durable?
|
64
|
+
# @option opts [Boolean] :auto_delete (false) Should this exchange be automatically deleted when it is no longer used?
|
65
|
+
# @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
66
|
+
#
|
67
|
+
# @see Bunny::Channel#topic
|
68
|
+
# @see Bunny::Channel#fanout
|
69
|
+
# @see Bunny::Channel#direct
|
70
|
+
# @see Bunny::Channel#headers
|
71
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
72
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
73
|
+
# @api public
|
74
|
+
def initialize(channel, type, name, opts = {})
|
75
|
+
@channel = channel
|
76
|
+
@name = name
|
77
|
+
@type = type
|
78
|
+
@options = self.class.add_default_options(name, opts)
|
79
|
+
|
80
|
+
@durable = @options[:durable]
|
81
|
+
@auto_delete = @options[:auto_delete]
|
82
|
+
@internal = @options[:internal]
|
83
|
+
@arguments = @options[:arguments]
|
84
|
+
|
85
|
+
@bindings = Set.new
|
86
|
+
|
87
|
+
declare! unless opts[:no_declare] || predeclared? || (@name == AMQ::Protocol::EMPTY_STRING)
|
88
|
+
|
89
|
+
@channel.register_exchange(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Boolean] true if this exchange was declared as durable (will survive broker restart).
|
93
|
+
# @api public
|
94
|
+
def durable?
|
95
|
+
@durable
|
96
|
+
end # durable?
|
97
|
+
|
98
|
+
# @return [Boolean] true if this exchange was declared as automatically deleted (deleted as soon as last consumer unbinds).
|
99
|
+
# @api public
|
100
|
+
def auto_delete?
|
101
|
+
@auto_delete
|
102
|
+
end # auto_delete?
|
103
|
+
|
104
|
+
# @return [Boolean] true if this exchange is internal (used solely for exchange-to-exchange
|
105
|
+
# bindings and cannot be published to by clients)
|
106
|
+
def internal?
|
107
|
+
@internal
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
111
|
+
# @api public
|
112
|
+
def arguments
|
113
|
+
@arguments
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# Publishes a message
|
118
|
+
#
|
119
|
+
# @param [String] payload Message payload. It will never be modified by Bunny or RabbitMQ in any way.
|
120
|
+
# @param [Hash] opts Message properties (metadata) and delivery settings
|
121
|
+
#
|
122
|
+
# @option opts [String] :routing_key Routing key
|
123
|
+
# @option opts [Boolean] :persistent Should the message be persisted to disk?
|
124
|
+
# @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
|
125
|
+
# @option opts [Integer] :timestamp A timestamp associated with this message
|
126
|
+
# @option opts [Integer] :expiration Expiration time after which the message will be deleted
|
127
|
+
# @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
|
128
|
+
# @option opts [String] :reply_to Queue name other apps should send the response to
|
129
|
+
# @option opts [String] :content_type Message content type (e.g. application/json)
|
130
|
+
# @option opts [String] :content_encoding Message content encoding (e.g. gzip)
|
131
|
+
# @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
|
132
|
+
# @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
|
133
|
+
# @option opts [String] :message_id Any message identifier
|
134
|
+
# @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
|
135
|
+
# @option opts [String] :app_id Optional application ID
|
136
|
+
#
|
137
|
+
# @return [Bunny::Exchange] Self
|
138
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
139
|
+
# @api public
|
140
|
+
def publish(payload, opts = {})
|
141
|
+
@channel.basic_publish(payload, self.name, (opts.delete(:routing_key) || opts.delete(:key)), opts)
|
142
|
+
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# Deletes the exchange unless it is predeclared
|
148
|
+
#
|
149
|
+
# @param [Hash] opts Options
|
150
|
+
#
|
151
|
+
# @option opts [Boolean] if_unused (false) Should this exchange be deleted only if it is no longer used
|
152
|
+
#
|
153
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
154
|
+
# @api public
|
155
|
+
def delete(opts = {})
|
156
|
+
@channel.deregister_exchange(self)
|
157
|
+
@channel.exchange_delete(@name, opts) unless predeclared?
|
158
|
+
end
|
159
|
+
|
160
|
+
# Binds an exchange to another (source) exchange using exchange.bind AMQP 0.9.1 extension
|
161
|
+
# that RabbitMQ provides.
|
162
|
+
#
|
163
|
+
# @param [String] source Source exchange name
|
164
|
+
# @param [Hash] opts Options
|
165
|
+
#
|
166
|
+
# @option opts [String] routing_key (nil) Routing key used for binding
|
167
|
+
# @option opts [Hash] arguments ({}) Optional arguments
|
168
|
+
#
|
169
|
+
# @return [Bunny::Exchange] Self
|
170
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
171
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
172
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
173
|
+
# @api public
|
174
|
+
def bind(source, opts = {})
|
175
|
+
@channel.exchange_bind(source, self, opts)
|
176
|
+
@bindings.add(source: source, opts: opts)
|
177
|
+
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
# Unbinds an exchange from another (source) exchange using exchange.unbind AMQP 0.9.1 extension
|
182
|
+
# that RabbitMQ provides.
|
183
|
+
#
|
184
|
+
# @param [String] source Source exchange name
|
185
|
+
# @param [Hash] opts Options
|
186
|
+
#
|
187
|
+
# @option opts [String] routing_key (nil) Routing key used for binding
|
188
|
+
# @option opts [Hash] arguments ({}) Optional arguments
|
189
|
+
#
|
190
|
+
# @return [Bunny::Exchange] Self
|
191
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
192
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
193
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
194
|
+
# @api public
|
195
|
+
def unbind(source, opts = {})
|
196
|
+
@channel.exchange_unbind(source, self, opts)
|
197
|
+
@bindings.delete(source: source, opts: opts)
|
198
|
+
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
# Defines a block that will handle returned messages
|
203
|
+
# @see http://rubybunny.info/articles/exchanges.html
|
204
|
+
# @api public
|
205
|
+
def on_return(&block)
|
206
|
+
@on_return = block
|
207
|
+
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
# Waits until all outstanding publisher confirms on the channel
|
212
|
+
# arrive.
|
213
|
+
#
|
214
|
+
# This is a convenience method that delegates to {Bunny::Channel#wait_for_confirms}
|
215
|
+
#
|
216
|
+
# @api public
|
217
|
+
def wait_for_confirms
|
218
|
+
@channel.wait_for_confirms
|
219
|
+
end
|
220
|
+
|
221
|
+
# @private
|
222
|
+
def recover_from_network_failure
|
223
|
+
declare! unless @options[:no_declare] ||predefined?
|
224
|
+
|
225
|
+
@bindings.each do |b|
|
226
|
+
bind(b[:source], b[:opts])
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
#
|
232
|
+
# Implementation
|
233
|
+
#
|
234
|
+
|
235
|
+
# @private
|
236
|
+
def handle_return(basic_return, properties, content)
|
237
|
+
if @on_return
|
238
|
+
@on_return.call(basic_return, properties, content)
|
239
|
+
else
|
240
|
+
# TODO: log a warning
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# @return [Boolean] true if this exchange is a pre-defined one (amq.direct, amq.fanout, amq.match and so on)
|
245
|
+
def predefined?
|
246
|
+
(@name == AMQ::Protocol::EMPTY_STRING) || !!(@name =~ /^amq\.(direct|fanout|topic|headers|match)/i)
|
247
|
+
end # predefined?
|
248
|
+
alias predeclared? predefined?
|
249
|
+
|
250
|
+
protected
|
251
|
+
|
252
|
+
# @private
|
253
|
+
def declare!
|
254
|
+
@channel.exchange_declare(@name, @type, @options)
|
255
|
+
end
|
256
|
+
|
257
|
+
# @private
|
258
|
+
def self.add_default_options(name, opts)
|
259
|
+
# :nowait is always false for Bunny
|
260
|
+
h = { :queue => name, :nowait => false }.merge(opts)
|
261
|
+
|
262
|
+
if name.empty?
|
263
|
+
{
|
264
|
+
:passive => false,
|
265
|
+
:durable => false,
|
266
|
+
:auto_delete => false,
|
267
|
+
:internal => false,
|
268
|
+
:arguments => nil
|
269
|
+
}.merge(h)
|
270
|
+
else
|
271
|
+
h
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Bunny
|
2
|
+
# @private
|
3
|
+
module Framing
|
4
|
+
ENCODINGS_SUPPORTED = defined? Encoding
|
5
|
+
HEADER_SLICE = (0..6).freeze
|
6
|
+
DATA_SLICE = (7..-1).freeze
|
7
|
+
PAYLOAD_SLICE = (0..-2).freeze
|
8
|
+
|
9
|
+
# @private
|
10
|
+
module String
|
11
|
+
class Frame < AMQ::Protocol::Frame
|
12
|
+
def self.decode(string)
|
13
|
+
header = string[HEADER_SLICE]
|
14
|
+
type, channel, size = self.decode_header(header)
|
15
|
+
data = string[DATA_SLICE]
|
16
|
+
payload = data[PAYLOAD_SLICE]
|
17
|
+
frame_end = data[-1, 1]
|
18
|
+
|
19
|
+
frame_end.force_encoding(AMQ::Protocol::Frame::FINAL_OCTET.encoding) if ENCODINGS_SUPPORTED
|
20
|
+
|
21
|
+
# 1) the size is miscalculated
|
22
|
+
if payload.bytesize != size
|
23
|
+
raise BadLengthError.new(size, payload.bytesize)
|
24
|
+
end
|
25
|
+
|
26
|
+
# 2) the size is OK, but the string doesn't end with FINAL_OCTET
|
27
|
+
raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
|
28
|
+
|
29
|
+
self.new(type, payload, channel)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end # String
|
33
|
+
|
34
|
+
|
35
|
+
# @private
|
36
|
+
module IO
|
37
|
+
class Frame < AMQ::Protocol::Frame
|
38
|
+
def self.decode(io)
|
39
|
+
header = io.read(7)
|
40
|
+
type, channel, size = self.decode_header(header)
|
41
|
+
data = io.read_fully(size + 1)
|
42
|
+
payload, frame_end = data[PAYLOAD_SLICE], data[-1, 1]
|
43
|
+
|
44
|
+
# 1) the size is miscalculated
|
45
|
+
if payload.bytesize != size
|
46
|
+
raise BadLengthError.new(size, payload.bytesize)
|
47
|
+
end
|
48
|
+
|
49
|
+
# 2) the size is OK, but the string doesn't end with FINAL_OCTET
|
50
|
+
raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
|
51
|
+
self.new(type, payload, channel)
|
52
|
+
end # self.from
|
53
|
+
end # Frame
|
54
|
+
end # IO
|
55
|
+
end # Framing
|
56
|
+
end # Bunny
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "bunny/versioned_delivery_tag"
|
2
|
+
|
3
|
+
module Bunny
|
4
|
+
# Wraps {AMQ::Protocol::Basic::GetOk} to
|
5
|
+
# provide access to the delivery properties as immutable hash as
|
6
|
+
# well as methods.
|
7
|
+
class GetResponse
|
8
|
+
|
9
|
+
#
|
10
|
+
# Behaviors
|
11
|
+
#
|
12
|
+
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
#
|
16
|
+
# API
|
17
|
+
#
|
18
|
+
|
19
|
+
# @return [Bunny::Channel] Channel this basic.get-ok response is on
|
20
|
+
attr_reader :channel
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def initialize(get_ok, channel)
|
24
|
+
@get_ok = get_ok
|
25
|
+
@hash = {
|
26
|
+
:delivery_tag => @get_ok.delivery_tag,
|
27
|
+
:redelivered => @get_ok.redelivered,
|
28
|
+
:exchange => @get_ok.exchange,
|
29
|
+
:routing_key => @get_ok.routing_key,
|
30
|
+
:channel => channel
|
31
|
+
}
|
32
|
+
@channel = channel
|
33
|
+
end
|
34
|
+
|
35
|
+
# Iterates over the delivery properties
|
36
|
+
# @see Enumerable#each
|
37
|
+
def each(*args, &block)
|
38
|
+
@hash.each(*args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Accesses delivery properties by key
|
42
|
+
# @see Hash#[]
|
43
|
+
def [](k)
|
44
|
+
@hash[k]
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Hash] Hash representation of this delivery info
|
48
|
+
def to_hash
|
49
|
+
@hash
|
50
|
+
end
|
51
|
+
|
52
|
+
# @private
|
53
|
+
def to_s
|
54
|
+
to_hash.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
# @private
|
58
|
+
def inspect
|
59
|
+
to_hash.inspect
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [String] Delivery identifier that is used to acknowledge, reject and nack deliveries
|
63
|
+
def delivery_tag
|
64
|
+
@get_ok.delivery_tag
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Boolean] true if this delivery is a redelivery (the message was requeued at least once)
|
68
|
+
def redelivered
|
69
|
+
@get_ok.redelivered
|
70
|
+
end
|
71
|
+
alias redelivered? redelivered
|
72
|
+
|
73
|
+
# @return [String] Name of the exchange this message was published to
|
74
|
+
def exchange
|
75
|
+
@get_ok.exchange
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [String] Routing key this message was published with
|
79
|
+
def routing_key
|
80
|
+
@get_ok.routing_key
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "amq/protocol/client"
|
3
|
+
require "amq/protocol/frame"
|
4
|
+
|
5
|
+
module Bunny
|
6
|
+
# Periodically sends heartbeats, keeping track of the last publishing activity.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
class HeartbeatSender
|
10
|
+
|
11
|
+
#
|
12
|
+
# API
|
13
|
+
#
|
14
|
+
|
15
|
+
def initialize(transport, logger)
|
16
|
+
@transport = transport
|
17
|
+
@logger = logger
|
18
|
+
@mutex = Monitor.new
|
19
|
+
|
20
|
+
@last_activity_time = Time.now
|
21
|
+
end
|
22
|
+
|
23
|
+
def start(period = 30)
|
24
|
+
@mutex.synchronize do
|
25
|
+
# calculate interval as half the given period plus
|
26
|
+
# some compensation for Ruby's implementation inaccuracy
|
27
|
+
# (we cannot get at the nanos level the Java client uses, and
|
28
|
+
# our approach is simplistic). MK.
|
29
|
+
@interval = [(period / 2) - 1, 0.4].max
|
30
|
+
|
31
|
+
@thread = Thread.new(&method(:run))
|
32
|
+
@thread.report_on_exception = false if @thread.respond_to?(:report_on_exception)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
@mutex.synchronize { @thread.exit }
|
38
|
+
end
|
39
|
+
|
40
|
+
def signal_activity!
|
41
|
+
@last_activity_time = Time.now
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def run
|
47
|
+
begin
|
48
|
+
loop do
|
49
|
+
self.beat
|
50
|
+
|
51
|
+
sleep @interval
|
52
|
+
end
|
53
|
+
rescue IOError => ioe
|
54
|
+
@logger.error "I/O error in the hearbeat sender: #{ioe.message}"
|
55
|
+
stop
|
56
|
+
rescue Exception => e
|
57
|
+
@logger.error "Error in the hearbeat sender: #{e.message}"
|
58
|
+
stop
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def beat
|
63
|
+
now = Time.now
|
64
|
+
|
65
|
+
if now > (@last_activity_time + @interval)
|
66
|
+
@logger.debug { "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}" }
|
67
|
+
@transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode, true)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "bunny/cruby/socket"
|
2
|
+
|
3
|
+
module Bunny
|
4
|
+
module JRuby
|
5
|
+
# TCP socket extension that uses Socket#readpartial to avoid excessive CPU
|
6
|
+
# burn after some time. See issue #165.
|
7
|
+
# @private
|
8
|
+
module Socket
|
9
|
+
include Bunny::Socket
|
10
|
+
|
11
|
+
def self.open(host, port, options = {})
|
12
|
+
socket = ::Socket.tcp(host, port, nil, nil,
|
13
|
+
connect_timeout: options[:connect_timeout])
|
14
|
+
if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
|
15
|
+
socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
|
16
|
+
end
|
17
|
+
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
|
18
|
+
socket.extend self
|
19
|
+
socket.options = { :host => host, :port => port }.merge(options)
|
20
|
+
socket
|
21
|
+
rescue Errno::ETIMEDOUT
|
22
|
+
raise ClientTimeout
|
23
|
+
end
|
24
|
+
|
25
|
+
# Reads given number of bytes with an optional timeout
|
26
|
+
#
|
27
|
+
# @param [Integer] count How many bytes to read
|
28
|
+
# @param [Integer] timeout Timeout
|
29
|
+
#
|
30
|
+
# @return [String] Data read from the socket
|
31
|
+
# @api public
|
32
|
+
def read_fully(count, timeout = nil)
|
33
|
+
value = ''
|
34
|
+
|
35
|
+
begin
|
36
|
+
loop do
|
37
|
+
value << read_nonblock(count - value.bytesize)
|
38
|
+
break if value.bytesize >= count
|
39
|
+
end
|
40
|
+
rescue EOFError
|
41
|
+
# JRuby specific fix via https://github.com/jruby/jruby/issues/1694#issuecomment-54873532
|
42
|
+
IO.select([self], nil, nil, timeout)
|
43
|
+
retry
|
44
|
+
rescue *READ_RETRY_EXCEPTION_CLASSES
|
45
|
+
if IO.select([self], nil, nil, timeout)
|
46
|
+
retry
|
47
|
+
else
|
48
|
+
raise Timeout::Error, "IO timeout when reading #{count} bytes"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
value
|
53
|
+
end # read_fully
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Bunny
|
2
|
+
module JRuby
|
3
|
+
begin
|
4
|
+
require "bunny/cruby/ssl_socket"
|
5
|
+
require "openssl"
|
6
|
+
|
7
|
+
# TLS-enabled TCP socket that implements convenience
|
8
|
+
# methods found in Bunny::Socket.
|
9
|
+
class SSLSocket < Bunny::SSLSocket
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super
|
13
|
+
@__bunny_socket_eof_flag__ = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Reads given number of bytes with an optional timeout
|
17
|
+
#
|
18
|
+
# @param [Integer] count How many bytes to read
|
19
|
+
# @param [Integer] timeout Timeout
|
20
|
+
#
|
21
|
+
# @return [String] Data read from the socket
|
22
|
+
# @api public
|
23
|
+
def read_fully(count, timeout = nil)
|
24
|
+
return nil if @__bunny_socket_eof_flag__
|
25
|
+
|
26
|
+
value = ''
|
27
|
+
begin
|
28
|
+
loop do
|
29
|
+
value << read_nonblock(count - value.bytesize)
|
30
|
+
break if value.bytesize >= count
|
31
|
+
end
|
32
|
+
rescue EOFError => e
|
33
|
+
@__bunny_socket_eof_flag__ = true
|
34
|
+
rescue OpenSSL::SSL::SSLError => e
|
35
|
+
if e.message == "read would block"
|
36
|
+
if IO.select([self], nil, nil, timeout)
|
37
|
+
retry
|
38
|
+
else
|
39
|
+
raise Timeout::Error, "IO timeout when reading #{count} bytes"
|
40
|
+
end
|
41
|
+
else
|
42
|
+
raise e
|
43
|
+
end
|
44
|
+
rescue *READ_RETRY_EXCEPTION_CLASSES => e
|
45
|
+
if IO.select([self], nil, nil, timeout)
|
46
|
+
retry
|
47
|
+
else
|
48
|
+
raise Timeout::Error, "IO timeout when reading #{count} bytes"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rescue LoadError => le
|
55
|
+
puts "Could not load OpenSSL"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|