gorgon 0.5.0.rc1 → 0.6.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +2 -4
- data/gorgon.gemspec +0 -1
- data/lib/gorgon/amqp_service.rb +5 -5
- data/lib/gorgon/gem_command_handler.rb +2 -1
- data/lib/gorgon/listener.rb +7 -5
- data/lib/gorgon/originator_protocol.rb +1 -0
- data/lib/gorgon/version.rb +1 -1
- data/lib/gorgon/worker_manager.rb +5 -2
- data/lib/gorgon_amq-protocol/.gitignore +15 -0
- data/lib/gorgon_amq-protocol/.gitmodules +3 -0
- data/lib/gorgon_amq-protocol/.rspec +3 -0
- data/lib/gorgon_amq-protocol/.travis.yml +19 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/bit_set.rb +82 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/endianness.rb +15 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/int_allocator.rb +96 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/pack.rb +53 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol.rb +4 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/client.rb +2322 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/constants.rb +22 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/exceptions.rb +60 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/float_32bit.rb +14 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/frame.rb +210 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table.rb +142 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_decoder.rb +190 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_encoder.rb +123 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/type_constants.rb +26 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/version.rb +5 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/settings.rb +114 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/uri.rb +37 -0
- data/lib/gorgon_bunny/lib/gorgon_amq/protocol/extensions.rb +16 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny.rb +89 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/credentials_encoder.rb +55 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/external_mechanism_encoder.rb +27 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/plain_mechanism_encoder.rb +19 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/channel.rb +1875 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/channel_id_allocator.rb +80 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/compatibility.rb +24 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/atomic_fixnum.rb +74 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/condition.rb +66 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/continuation_queue.rb +41 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/linked_continuation_queue.rb +61 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer.rb +123 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_tag_generator.rb +23 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_work_pool.rb +94 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/delivery_info.rb +93 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/exceptions.rb +236 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/exchange.rb +271 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/framing.rb +56 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/heartbeat_sender.rb +70 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/message_properties.rb +119 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/queue.rb +387 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/reader_loop.rb +116 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/return_info.rb +74 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/session.rb +1044 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/socket.rb +83 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/ssl_socket.rb +57 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/system_timer.rb +20 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/test_kit.rb +27 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/timeout.rb +18 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/transport.rb +398 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/version.rb +6 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/versioned_delivery_tag.rb +28 -0
- data/spec/crash_reporter_spec.rb +1 -1
- data/spec/gem_command_handler_spec.rb +2 -2
- data/spec/listener_spec.rb +5 -5
- data/spec/worker_manager_spec.rb +3 -3
- metadata +56 -17
@@ -0,0 +1,271 @@
|
|
1
|
+
require "gorgon_bunny/compatibility"
|
2
|
+
|
3
|
+
module GorgonBunny
|
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
|
+
include GorgonBunny::Compatibility
|
11
|
+
|
12
|
+
|
13
|
+
#
|
14
|
+
# API
|
15
|
+
#
|
16
|
+
|
17
|
+
# @return [GorgonBunny::Channel]
|
18
|
+
attr_reader :channel
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
attr_reader :name
|
22
|
+
|
23
|
+
# Type of this exchange (one of: :direct, :fanout, :topic, :headers).
|
24
|
+
# @return [Symbol]
|
25
|
+
attr_reader :type
|
26
|
+
|
27
|
+
# @return [Symbol]
|
28
|
+
# @api plugin
|
29
|
+
attr_reader :status
|
30
|
+
|
31
|
+
# Options hash this exchange instance was instantiated with
|
32
|
+
# @return [Hash]
|
33
|
+
attr_accessor :opts
|
34
|
+
|
35
|
+
|
36
|
+
# The default exchange. Default exchange is a direct exchange that is predefined.
|
37
|
+
# It cannot be removed. Every queue is bind to this (direct) exchange by default with
|
38
|
+
# the following routing semantics: messages will be routed to the queue withe same
|
39
|
+
# same name as message's routing key. In other words, if a message is published with
|
40
|
+
# a routing key of "weather.usa.ca.sandiego" and there is a queue Q with this name,
|
41
|
+
# that message will be routed to Q.
|
42
|
+
#
|
43
|
+
# @param [GorgonBunny::Channel] channel_or_connection Channel to use. {GorgonBunny::Session} instances
|
44
|
+
# are only supported for backwards compatibility.
|
45
|
+
#
|
46
|
+
# @example Publishing a messages to the tasks queue
|
47
|
+
# channel = GorgonBunny::Channel.new(connection)
|
48
|
+
# tasks_queue = channel.queue("tasks")
|
49
|
+
# GorgonBunny::Exchange.default(channel).publish("make clean", routing_key => "tasks")
|
50
|
+
#
|
51
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
52
|
+
# @see http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.1.2.4)
|
53
|
+
# @note Do not confuse default exchange with amq.direct: amq.direct is a pre-defined direct
|
54
|
+
# exchange that doesn't have any special routing semantics.
|
55
|
+
# @return [Exchange] An instance that corresponds to the default exchange (of type direct).
|
56
|
+
# @api public
|
57
|
+
def self.default(channel_or_connection)
|
58
|
+
self.new(channel_from(channel_or_connection), :direct, GorgonAMQ::Protocol::EMPTY_STRING, :no_declare => true)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [GorgonBunny::Channel] channel_or_connection Channel this exchange will use. {GorgonBunny::Session} instances are supported only for
|
62
|
+
# backwards compatibility with 0.8.
|
63
|
+
# @param [Symbol,String] type Exchange type
|
64
|
+
# @param [String] name Exchange name
|
65
|
+
# @param [Hash] opts Exchange properties
|
66
|
+
#
|
67
|
+
# @option opts [Boolean] :durable (false) Should this exchange be durable?
|
68
|
+
# @option opts [Boolean] :auto_delete (false) Should this exchange be automatically deleted when it is no longer used?
|
69
|
+
# @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
70
|
+
#
|
71
|
+
# @see GorgonBunny::Channel#topic
|
72
|
+
# @see GorgonBunny::Channel#fanout
|
73
|
+
# @see GorgonBunny::Channel#direct
|
74
|
+
# @see GorgonBunny::Channel#headers
|
75
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
76
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
77
|
+
# @api public
|
78
|
+
def initialize(channel_or_connection, type, name, opts = {})
|
79
|
+
# old GorgonBunny versions pass a connection here. In that case,
|
80
|
+
# we just use default channel from it. MK.
|
81
|
+
@channel = channel_from(channel_or_connection)
|
82
|
+
@name = name
|
83
|
+
@type = type
|
84
|
+
@options = self.class.add_default_options(name, opts)
|
85
|
+
|
86
|
+
@durable = @options[:durable]
|
87
|
+
@auto_delete = @options[:auto_delete]
|
88
|
+
@arguments = @options[:arguments]
|
89
|
+
|
90
|
+
declare! unless opts[:no_declare] || predeclared? || (@name == GorgonAMQ::Protocol::EMPTY_STRING)
|
91
|
+
|
92
|
+
@channel.register_exchange(self)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Boolean] true if this exchange was declared as durable (will survive broker restart).
|
96
|
+
# @api public
|
97
|
+
def durable?
|
98
|
+
@durable
|
99
|
+
end # durable?
|
100
|
+
|
101
|
+
# @return [Boolean] true if this exchange was declared as automatically deleted (deleted as soon as last consumer unbinds).
|
102
|
+
# @api public
|
103
|
+
def auto_delete?
|
104
|
+
@auto_delete
|
105
|
+
end # auto_delete?
|
106
|
+
|
107
|
+
# @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
108
|
+
# @api public
|
109
|
+
def arguments
|
110
|
+
@arguments
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Publishes a message
|
115
|
+
#
|
116
|
+
# @param [String] payload Message payload. It will never be modified by GorgonBunny or RabbitMQ in any way.
|
117
|
+
# @param [Hash] opts Message properties (metadata) and delivery settings
|
118
|
+
#
|
119
|
+
# @option opts [String] :routing_key Routing key
|
120
|
+
# @option opts [Boolean] :persistent Should the message be persisted to disk?
|
121
|
+
# @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
|
122
|
+
# @option opts [Integer] :timestamp A timestamp associated with this message
|
123
|
+
# @option opts [Integer] :expiration Expiration time after which the message will be deleted
|
124
|
+
# @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
|
125
|
+
# @option opts [String] :reply_to Queue name other apps should send the response to
|
126
|
+
# @option opts [String] :content_type Message content type (e.g. application/json)
|
127
|
+
# @option opts [String] :content_encoding Message content encoding (e.g. gzip)
|
128
|
+
# @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
|
129
|
+
# @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
|
130
|
+
# @option opts [String] :message_id Any message identifier
|
131
|
+
# @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
|
132
|
+
# @option opts [String] :app_id Optional application ID
|
133
|
+
#
|
134
|
+
# @return [GorgonBunny::Exchange] Self
|
135
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
136
|
+
# @api public
|
137
|
+
def publish(payload, opts = {})
|
138
|
+
@channel.basic_publish(payload, self.name, (opts.delete(:routing_key) || opts.delete(:key)), opts)
|
139
|
+
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Deletes the exchange unless it is predeclared
|
145
|
+
#
|
146
|
+
# @param [Hash] opts Options
|
147
|
+
#
|
148
|
+
# @option opts [Boolean] if_unused (false) Should this exchange be deleted only if it is no longer used
|
149
|
+
#
|
150
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
151
|
+
# @api public
|
152
|
+
def delete(opts = {})
|
153
|
+
@channel.deregister_exchange(self)
|
154
|
+
@channel.exchange_delete(@name, opts) unless predeclared?
|
155
|
+
end
|
156
|
+
|
157
|
+
# Binds an exchange to another (source) exchange using exchange.bind AMQP 0.9.1 extension
|
158
|
+
# that RabbitMQ provides.
|
159
|
+
#
|
160
|
+
# @param [String] source Source exchange name
|
161
|
+
# @param [Hash] opts Options
|
162
|
+
#
|
163
|
+
# @option opts [String] routing_key (nil) Routing key used for binding
|
164
|
+
# @option opts [Hash] arguments ({}) Optional arguments
|
165
|
+
#
|
166
|
+
# @return [GorgonBunny::Exchange] Self
|
167
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
168
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
169
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
170
|
+
# @api public
|
171
|
+
def bind(source, opts = {})
|
172
|
+
@channel.exchange_bind(source, self, opts)
|
173
|
+
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
# Unbinds an exchange from another (source) exchange using exchange.unbind AMQP 0.9.1 extension
|
178
|
+
# that RabbitMQ provides.
|
179
|
+
#
|
180
|
+
# @param [String] source Source exchange name
|
181
|
+
# @param [Hash] opts Options
|
182
|
+
#
|
183
|
+
# @option opts [String] routing_key (nil) Routing key used for binding
|
184
|
+
# @option opts [Hash] arguments ({}) Optional arguments
|
185
|
+
#
|
186
|
+
# @return [GorgonBunny::Exchange] Self
|
187
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
188
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
189
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
190
|
+
# @api public
|
191
|
+
def unbind(source, opts = {})
|
192
|
+
@channel.exchange_unbind(source, self, opts)
|
193
|
+
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
197
|
+
# Defines a block that will handle returned messages
|
198
|
+
# @see http://rubybunny.info/articles/exchanges.html
|
199
|
+
# @api public
|
200
|
+
def on_return(&block)
|
201
|
+
@on_return = block
|
202
|
+
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
# Waits until all outstanding publisher confirms on the channel
|
207
|
+
# arrive.
|
208
|
+
#
|
209
|
+
# This is a convenience method that delegates to {GorgonBunny::Channel#wait_for_confirms}
|
210
|
+
#
|
211
|
+
# @api public
|
212
|
+
def wait_for_confirms
|
213
|
+
@channel.wait_for_confirms
|
214
|
+
end
|
215
|
+
|
216
|
+
# @private
|
217
|
+
def recover_from_network_failure
|
218
|
+
# puts "Recovering exchange #{@name} from network failure"
|
219
|
+
declare! unless predefined?
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
#
|
224
|
+
# Implementation
|
225
|
+
#
|
226
|
+
|
227
|
+
# @private
|
228
|
+
def handle_return(basic_return, properties, content)
|
229
|
+
if @on_return
|
230
|
+
@on_return.call(basic_return, properties, content)
|
231
|
+
else
|
232
|
+
# TODO: log a warning
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# @return [Boolean] true if this exchange is a pre-defined one (amq.direct, amq.fanout, amq.match and so on)
|
237
|
+
def predefined?
|
238
|
+
(@name == GorgonAMQ::Protocol::EMPTY_STRING) || !!(@name =~ /^amq\.(direct|fanout|topic|headers|match)/i)
|
239
|
+
end # predefined?
|
240
|
+
alias predeclared? predefined?
|
241
|
+
|
242
|
+
protected
|
243
|
+
|
244
|
+
# @private
|
245
|
+
def declare!
|
246
|
+
@channel.exchange_declare(@name, @type, @options)
|
247
|
+
end
|
248
|
+
|
249
|
+
# @private
|
250
|
+
def self.add_default_options(name, opts, block)
|
251
|
+
{ :exchange => name, :nowait => (block.nil? && !name.empty?) }.merge(opts)
|
252
|
+
end
|
253
|
+
|
254
|
+
# @private
|
255
|
+
def self.add_default_options(name, opts)
|
256
|
+
# :nowait is always false for GorgonBunny
|
257
|
+
h = { :queue => name, :nowait => false }.merge(opts)
|
258
|
+
|
259
|
+
if name.empty?
|
260
|
+
{
|
261
|
+
:passive => false,
|
262
|
+
:durable => false,
|
263
|
+
:auto_delete => false,
|
264
|
+
:arguments => nil
|
265
|
+
}.merge(h)
|
266
|
+
else
|
267
|
+
h
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module GorgonBunny
|
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 < GorgonAMQ::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(GorgonAMQ::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 != GorgonAMQ::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 < GorgonAMQ::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 != GorgonAMQ::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 # GorgonBunny
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "gorgon_amq/protocol/client"
|
3
|
+
require "gorgon_amq/protocol/frame"
|
4
|
+
|
5
|
+
module GorgonBunny
|
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
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
@mutex.synchronize { @thread.exit }
|
37
|
+
end
|
38
|
+
|
39
|
+
def signal_activity!
|
40
|
+
@last_activity_time = Time.now
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def run
|
46
|
+
begin
|
47
|
+
loop do
|
48
|
+
self.beat
|
49
|
+
|
50
|
+
sleep @interval
|
51
|
+
end
|
52
|
+
rescue IOError => ioe
|
53
|
+
@logger.error "I/O error in the hearbeat sender: #{ioe.message}"
|
54
|
+
stop
|
55
|
+
rescue Exception => e
|
56
|
+
@logger.error "Error in the hearbeat sender: #{e.message}"
|
57
|
+
stop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def beat
|
62
|
+
now = Time.now
|
63
|
+
|
64
|
+
if now > (@last_activity_time + @interval)
|
65
|
+
@logger.debug "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}"
|
66
|
+
@transport.write_without_timeout(GorgonAMQ::Protocol::HeartbeatFrame.encode)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module GorgonBunny
|
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
|