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