hutch 0.25.0.pre.rc1-java → 2.0.0-java
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 +5 -5
- data/CHANGELOG.md +402 -5
- data/LICENSE +1 -0
- data/README.md +157 -47
- data/bin/ci/before_build.sh +20 -0
- data/bin/ci/before_build_docker.sh +15 -0
- data/bin/ci/install_on_debian.sh +46 -0
- data/lib/hutch/acknowledgements/nack_on_all_failures.rb +1 -1
- data/lib/hutch/adapters/bunny.rb +29 -1
- data/lib/hutch/adapters/march_hare.rb +24 -0
- data/lib/hutch/broker.rb +122 -36
- data/lib/hutch/cli.rb +23 -12
- data/lib/hutch/config.rb +45 -13
- data/lib/hutch/consumer.rb +63 -4
- data/lib/hutch/error_handlers/airbrake.rb +20 -2
- data/lib/hutch/error_handlers/base.rb +15 -0
- data/lib/hutch/error_handlers/bugsnag.rb +30 -0
- data/lib/hutch/error_handlers/honeybadger.rb +11 -3
- data/lib/hutch/error_handlers/logger.rb +7 -2
- data/lib/hutch/error_handlers/rollbar.rb +28 -0
- data/lib/hutch/error_handlers/sentry.rb +13 -11
- data/lib/hutch/error_handlers.rb +2 -1
- data/lib/hutch/publisher.rb +1 -1
- data/lib/hutch/serializers/json.rb +3 -3
- data/lib/hutch/tracers/datadog.rb +18 -0
- data/lib/hutch/tracers.rb +1 -1
- data/lib/hutch/version.rb +1 -2
- data/lib/hutch/waiter.rb +1 -1
- data/lib/hutch/worker.rb +74 -8
- data/lib/hutch.rb +8 -4
- data/lib/yard-settings/yard-settings.rb +2 -2
- data/spec/hutch/broker_spec.rb +222 -10
- data/spec/hutch/cli_spec.rb +25 -9
- data/spec/hutch/config_spec.rb +84 -12
- data/spec/hutch/consumer_spec.rb +93 -4
- data/spec/hutch/error_handlers/airbrake_spec.rb +23 -1
- data/spec/hutch/error_handlers/bugsnag_spec.rb +56 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +22 -1
- data/spec/hutch/error_handlers/logger_spec.rb +12 -1
- data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +27 -2
- data/spec/hutch/message_spec.rb +1 -1
- data/spec/hutch/tracers/datadog_spec.rb +46 -0
- data/spec/hutch/waiter_spec.rb +2 -2
- data/spec/hutch/worker_spec.rb +139 -25
- data/spec/integration/channel_recovery_spec.rb +187 -0
- data/spec/integration/publish_consume_spec.rb +47 -0
- metadata +35 -61
- data/.gitignore +0 -10
- data/.rspec +0 -1
- data/.travis.yml +0 -19
- data/.yardopts +0 -5
- data/Gemfile +0 -33
- data/Guardfile +0 -14
- data/Rakefile +0 -21
- data/examples/consumer.rb +0 -13
- data/examples/producer.rb +0 -10
- data/hutch.gemspec +0 -29
- data/lib/hutch/error_handlers/opbeat.rb +0 -24
- data/lib/hutch/tracers/opbeat.rb +0 -37
- data/spec/hutch/error_handlers/opbeat_spec.rb +0 -22
- data/spec/spec_helper.rb +0 -42
- data/spec/tracers/opbeat_spec.rb +0 -44
data/lib/hutch/broker.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'uri'
|
|
1
2
|
require 'active_support/core_ext/object/blank'
|
|
2
3
|
|
|
3
4
|
require 'carrot-top'
|
|
@@ -11,6 +12,24 @@ module Hutch
|
|
|
11
12
|
|
|
12
13
|
attr_accessor :connection, :channel, :exchange, :api_client
|
|
13
14
|
|
|
15
|
+
|
|
16
|
+
DEFAULT_AMQP_PORT =
|
|
17
|
+
case RUBY_ENGINE
|
|
18
|
+
when "jruby" then
|
|
19
|
+
com.rabbitmq.client.ConnectionFactory::DEFAULT_AMQP_PORT
|
|
20
|
+
when "ruby" then
|
|
21
|
+
AMQ::Protocol::DEFAULT_PORT
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
DEFAULT_AMQPS_PORT =
|
|
25
|
+
case RUBY_ENGINE
|
|
26
|
+
when "jruby" then
|
|
27
|
+
com.rabbitmq.client.ConnectionFactory::DEFAULT_AMQP_OVER_SSL_PORT
|
|
28
|
+
when "ruby" then
|
|
29
|
+
AMQ::Protocol::TLS_PORT
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
14
33
|
# @param config [nil,Hash] Configuration override
|
|
15
34
|
def initialize(config = nil)
|
|
16
35
|
@config = config || Hutch::Config
|
|
@@ -95,6 +114,8 @@ module Hutch
|
|
|
95
114
|
logger.info 'enabling publisher confirms'
|
|
96
115
|
ch.confirm_select
|
|
97
116
|
end
|
|
117
|
+
|
|
118
|
+
connection.install_channel_recovery(ch)
|
|
98
119
|
end
|
|
99
120
|
end
|
|
100
121
|
|
|
@@ -102,13 +123,23 @@ module Hutch
|
|
|
102
123
|
@channel = open_channel
|
|
103
124
|
end
|
|
104
125
|
|
|
126
|
+
# Closing the old channel makes RabbitMQ forget the consumers on it
|
|
127
|
+
# and requeue their unacknowledged deliveries.
|
|
128
|
+
def replace_channel!
|
|
129
|
+
close_consumer_channel
|
|
130
|
+
open_channel!
|
|
131
|
+
declare_exchange!
|
|
132
|
+
declare_publisher!
|
|
133
|
+
end
|
|
134
|
+
|
|
105
135
|
def declare_exchange(ch = channel)
|
|
106
136
|
exchange_name = @config[:mq_exchange]
|
|
137
|
+
exchange_type = @config[:mq_exchange_type]
|
|
107
138
|
exchange_options = { durable: true }.merge(@config[:mq_exchange_options])
|
|
108
139
|
logger.info "using topic exchange '#{exchange_name}'"
|
|
109
140
|
|
|
110
141
|
with_bunny_precondition_handler('exchange') do
|
|
111
|
-
|
|
142
|
+
Adapter.new_exchange(ch, exchange_type, exchange_name, exchange_options)
|
|
112
143
|
end
|
|
113
144
|
end
|
|
114
145
|
|
|
@@ -151,24 +182,35 @@ module Hutch
|
|
|
151
182
|
@config[:tracer] && @config[:tracer] != Hutch::Tracers::NullTracer
|
|
152
183
|
end
|
|
153
184
|
|
|
154
|
-
# Create / get a durable queue
|
|
155
|
-
def queue(name,
|
|
185
|
+
# Create / get a durable queue.
|
|
186
|
+
def queue(name, options = {})
|
|
156
187
|
with_bunny_precondition_handler('queue') do
|
|
157
|
-
|
|
158
|
-
name = name.prepend(namespace + ":") if namespace.present?
|
|
159
|
-
channel.queue(name, durable: true, arguments: arguments)
|
|
188
|
+
channel.queue(name, **options)
|
|
160
189
|
end
|
|
161
190
|
end
|
|
162
191
|
|
|
192
|
+
def queue_exists?(name)
|
|
193
|
+
connection.queue_exists?(name)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Apply the configured namespace prefix to a queue name.
|
|
197
|
+
def namespaced_queue_name(name)
|
|
198
|
+
namespace = @config[:namespace].to_s.downcase.gsub(/[^-_:\.\w]/, "")
|
|
199
|
+
namespace.present? ? "#{namespace}:#{name}" : name
|
|
200
|
+
end
|
|
201
|
+
|
|
163
202
|
# Return a mapping of queue names to the routing keys they're bound to.
|
|
164
203
|
def bindings
|
|
165
204
|
results = Hash.new { |hash, key| hash[key] = [] }
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
205
|
+
|
|
206
|
+
filtered = api_client.bindings.
|
|
207
|
+
reject { |b| b['destination'] == b['routing_key'] }.
|
|
208
|
+
select { |b| b['source'] == @config[:mq_exchange] && b['vhost'] == vhost }
|
|
209
|
+
|
|
210
|
+
filtered.each do |binding|
|
|
170
211
|
results[binding['destination']] << binding['routing_key']
|
|
171
212
|
end
|
|
213
|
+
|
|
172
214
|
results
|
|
173
215
|
end
|
|
174
216
|
|
|
@@ -176,8 +218,8 @@ module Hutch
|
|
|
176
218
|
def unbind_redundant_bindings(queue, routing_keys)
|
|
177
219
|
return unless http_api_use_enabled?
|
|
178
220
|
|
|
179
|
-
bindings.
|
|
180
|
-
|
|
221
|
+
filtered = bindings.select { |dest, keys| dest == queue.name }
|
|
222
|
+
filtered.each do |dest, keys|
|
|
181
223
|
keys.reject { |key| routing_keys.include?(key) }.each do |key|
|
|
182
224
|
logger.debug "removing redundant binding #{queue.name} <--> #{key}"
|
|
183
225
|
queue.unbind(exchange, routing_key: key)
|
|
@@ -202,29 +244,26 @@ module Hutch
|
|
|
202
244
|
if defined?(JRUBY_VERSION)
|
|
203
245
|
channel.close
|
|
204
246
|
else
|
|
205
|
-
|
|
206
|
-
channel_work_pool.shutdown
|
|
207
|
-
# Give `timeout` seconds to jobs that are still being processed
|
|
208
|
-
channel_work_pool.join(@config[:graceful_exit_timeout])
|
|
209
|
-
# If after `timeout` they are still running, they are killed
|
|
210
|
-
channel_work_pool.kill
|
|
247
|
+
drain_consumer_work_pool
|
|
211
248
|
end
|
|
212
249
|
end
|
|
213
250
|
|
|
214
|
-
|
|
215
|
-
|
|
251
|
+
# Delivery tags are scoped to their channel: a tag from a replaced one
|
|
252
|
+
# is dropped, the server has requeued that delivery anyway.
|
|
253
|
+
def requeue(delivery_tag, channel: self.channel)
|
|
254
|
+
channel.reject(delivery_tag, true) if current_channel?(channel)
|
|
216
255
|
end
|
|
217
256
|
|
|
218
|
-
def reject(delivery_tag, requeue=false)
|
|
219
|
-
channel.reject(delivery_tag, requeue)
|
|
257
|
+
def reject(delivery_tag, requeue = false, channel: self.channel)
|
|
258
|
+
channel.reject(delivery_tag, requeue) if current_channel?(channel)
|
|
220
259
|
end
|
|
221
260
|
|
|
222
|
-
def ack(delivery_tag)
|
|
223
|
-
channel.ack(delivery_tag, false)
|
|
261
|
+
def ack(delivery_tag, channel: self.channel)
|
|
262
|
+
channel.ack(delivery_tag, false) if current_channel?(channel)
|
|
224
263
|
end
|
|
225
264
|
|
|
226
|
-
def nack(delivery_tag)
|
|
227
|
-
channel.nack(delivery_tag, false, false)
|
|
265
|
+
def nack(delivery_tag, channel: self.channel)
|
|
266
|
+
channel.nack(delivery_tag, false, false) if current_channel?(channel)
|
|
228
267
|
end
|
|
229
268
|
|
|
230
269
|
def publish(*args)
|
|
@@ -246,15 +285,39 @@ module Hutch
|
|
|
246
285
|
|
|
247
286
|
private
|
|
248
287
|
|
|
288
|
+
def current_channel?(ch)
|
|
289
|
+
ch.equal?(channel)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def close_consumer_channel
|
|
293
|
+
return unless @channel && @channel.open?
|
|
294
|
+
|
|
295
|
+
drain_consumer_work_pool unless defined?(JRUBY_VERSION)
|
|
296
|
+
@channel.close
|
|
297
|
+
rescue Hutch::Adapter::ChannelAlreadyClosed
|
|
298
|
+
# The server closed the channel first: same outcome.
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Bunny kills the consumer work pool when the channel closes. Draining
|
|
302
|
+
# first gives the running handlers `graceful_exit_timeout` to finish.
|
|
303
|
+
def drain_consumer_work_pool
|
|
304
|
+
channel_work_pool.shutdown
|
|
305
|
+
channel_work_pool.join(@config[:graceful_exit_timeout])
|
|
306
|
+
channel_work_pool.kill
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
Config = Struct.new(:host, :port, :username, :password, :ssl, :protocol, :sanitized_uri)
|
|
310
|
+
private_constant :Config
|
|
311
|
+
|
|
249
312
|
def api_config
|
|
250
|
-
@api_config ||=
|
|
313
|
+
@api_config ||= Config.new.tap do |config|
|
|
251
314
|
config.host = @config[:mq_api_host]
|
|
252
315
|
config.port = @config[:mq_api_port]
|
|
253
316
|
config.username = @config[:mq_username]
|
|
254
317
|
config.password = @config[:mq_password]
|
|
255
318
|
config.ssl = @config[:mq_api_ssl]
|
|
256
319
|
config.protocol = config.ssl ? "https://" : "http://"
|
|
257
|
-
config.sanitized_uri = "#{config.protocol}#{config.username}@#{config.host}:#{config.port}/"
|
|
320
|
+
config.sanitized_uri = "#{config.protocol}#{URI.encode_uri_component(config.username.to_s)}@#{config.host}:#{config.port}/"
|
|
258
321
|
end
|
|
259
322
|
end
|
|
260
323
|
|
|
@@ -264,7 +327,8 @@ module Hutch
|
|
|
264
327
|
{}.tap do |params|
|
|
265
328
|
params[:host] = @config[:mq_host]
|
|
266
329
|
params[:port] = @config[:mq_port]
|
|
267
|
-
params[:vhost] =
|
|
330
|
+
params[:vhost] = vhost
|
|
331
|
+
params[:auth_mechanism] = @config[:mq_auth_mechanism]
|
|
268
332
|
params[:username] = @config[:mq_username]
|
|
269
333
|
params[:password] = @config[:mq_password]
|
|
270
334
|
params[:tls] = @config[:mq_tls]
|
|
@@ -275,13 +339,15 @@ module Hutch
|
|
|
275
339
|
params[:tls_ca_certificates] = @config[:mq_tls_ca_certificates]
|
|
276
340
|
end
|
|
277
341
|
params[:heartbeat] = @config[:heartbeat]
|
|
342
|
+
params[:client_properties] = @config[:mq_client_properties]
|
|
343
|
+
params[:connection_name] = @config[:connection_name]
|
|
278
344
|
params[:connection_timeout] = @config[:connection_timeout]
|
|
279
345
|
params[:read_timeout] = @config[:read_timeout]
|
|
280
346
|
params[:write_timeout] = @config[:write_timeout]
|
|
281
347
|
|
|
282
348
|
|
|
283
|
-
params[:automatically_recover] =
|
|
284
|
-
params[:network_recovery_interval] =
|
|
349
|
+
params[:automatically_recover] = @config[:automatically_recover]
|
|
350
|
+
params[:network_recovery_interval] = @config[:network_recovery_interval]
|
|
285
351
|
|
|
286
352
|
params[:logger] = @config[:client_logger] if @config[:client_logger]
|
|
287
353
|
end
|
|
@@ -292,18 +358,38 @@ module Hutch
|
|
|
292
358
|
|
|
293
359
|
u = URI.parse(@config[:uri])
|
|
294
360
|
|
|
361
|
+
@config[:mq_tls] = u.scheme == 'amqps'
|
|
295
362
|
@config[:mq_host] = u.host
|
|
296
|
-
@config[:mq_port] = u.port
|
|
297
|
-
@config[:mq_vhost] = u
|
|
298
|
-
@config[:mq_username] = u.user
|
|
299
|
-
@config[:mq_password] = u.password
|
|
363
|
+
@config[:mq_port] = u.port || default_mq_port
|
|
364
|
+
@config[:mq_vhost] = parse_vhost(u)
|
|
365
|
+
@config[:mq_username] = u.user && URI.decode_uri_component(u.user)
|
|
366
|
+
@config[:mq_password] = u.password && URI.decode_uri_component(u.password)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Resolved here rather than in #connection_params, so that
|
|
370
|
+
# Config[:mq_vhost] holds the name the HTTP API reports too.
|
|
371
|
+
def parse_vhost(uri)
|
|
372
|
+
URI.decode_uri_component(uri.path.sub(%r{\A/}, "")).presence ||
|
|
373
|
+
Hutch::Adapter::DEFAULT_VHOST
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# The vhost Hutch connects to. Config[:mq_vhost] can still be blank when it
|
|
377
|
+
# was set directly rather than through a URI, while the broker and its HTTP
|
|
378
|
+
# API call the default vhost `/`.
|
|
379
|
+
def vhost
|
|
380
|
+
@config[:mq_vhost].presence || Hutch::Adapter::DEFAULT_VHOST
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def default_mq_port
|
|
384
|
+
@config[:mq_tls] ? DEFAULT_AMQPS_PORT : DEFAULT_AMQP_PORT
|
|
300
385
|
end
|
|
301
386
|
|
|
302
387
|
def sanitized_uri
|
|
303
388
|
p = connection_params
|
|
304
389
|
scheme = p[:tls] ? "amqps" : "amqp"
|
|
305
390
|
|
|
306
|
-
"#{scheme}://#{p[:username]
|
|
391
|
+
"#{scheme}://#{URI.encode_uri_component(p[:username].to_s)}" \
|
|
392
|
+
"@#{p[:host]}:#{p[:port]}/#{URI.encode_uri_component(p[:vhost].sub(%r{\A/}, ''))}"
|
|
307
393
|
end
|
|
308
394
|
|
|
309
395
|
def with_authentication_error_handler
|
data/lib/hutch/cli.rb
CHANGED
|
@@ -33,6 +33,20 @@ module Hutch
|
|
|
33
33
|
def load_app
|
|
34
34
|
# Try to load a Rails app in the current directory
|
|
35
35
|
load_rails_app('.') if Hutch::Config.autoload_rails
|
|
36
|
+
set_up_code_paths!
|
|
37
|
+
|
|
38
|
+
# Because of the order things are required when we run the Hutch binary
|
|
39
|
+
# in hutch/bin, the sentry-ruby gem gets required **after** the error
|
|
40
|
+
# handlers are set up. Due to this, we never got any Sentry notifications
|
|
41
|
+
# when an error occurred in any of the consumers.
|
|
42
|
+
if defined?(Sentry)
|
|
43
|
+
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def set_up_code_paths!
|
|
36
50
|
Hutch::Config.require_paths.each do |path|
|
|
37
51
|
# See if each path is a Rails app. If so, try to load it.
|
|
38
52
|
next if load_rails_app(path)
|
|
@@ -48,19 +62,9 @@ module Hutch
|
|
|
48
62
|
return false
|
|
49
63
|
ensure
|
|
50
64
|
# Clean up load path
|
|
51
|
-
$LOAD_PATH.
|
|
65
|
+
$LOAD_PATH.delete('.')
|
|
52
66
|
end
|
|
53
67
|
end
|
|
54
|
-
|
|
55
|
-
# Because of the order things are required when we run the Hutch binary
|
|
56
|
-
# in hutch/bin, the Sentry Raven gem gets required **after** the error
|
|
57
|
-
# handlers are set up. Due to this, we never got any Sentry notifications
|
|
58
|
-
# when an error occurred in any of the consumers.
|
|
59
|
-
if defined?(Raven)
|
|
60
|
-
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
true
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
def load_rails_app(path)
|
|
@@ -90,6 +94,9 @@ module Hutch
|
|
|
90
94
|
@worker.run
|
|
91
95
|
:success
|
|
92
96
|
rescue ConnectionError, AuthenticationError, WorkerSetupError => ex
|
|
97
|
+
Hutch::Config[:error_handlers].each do |backend|
|
|
98
|
+
backend.handle_setup_exception(ex)
|
|
99
|
+
end
|
|
93
100
|
logger.fatal ex.message
|
|
94
101
|
:error
|
|
95
102
|
end
|
|
@@ -189,6 +196,10 @@ module Hutch
|
|
|
189
196
|
Hutch::Config.pidfile = pidfile
|
|
190
197
|
end
|
|
191
198
|
|
|
199
|
+
opts.on('--only-group GROUP', 'Load only consumers in this group') do |group|
|
|
200
|
+
Hutch::Config.group = group
|
|
201
|
+
end
|
|
202
|
+
|
|
192
203
|
opts.on('--version', 'Print the version and exit') do
|
|
193
204
|
puts "hutch v#{VERSION}"
|
|
194
205
|
exit 0
|
|
@@ -220,7 +231,7 @@ module Hutch
|
|
|
220
231
|
end
|
|
221
232
|
|
|
222
233
|
def abort_without_file(file, file_description, &block)
|
|
223
|
-
abort_with_message("#{file_description} '#{file}' not found") unless File.
|
|
234
|
+
abort_with_message("#{file_description} '#{file}' not found") unless File.exist?(file)
|
|
224
235
|
|
|
225
236
|
yield
|
|
226
237
|
end
|
data/lib/hutch/config.rb
CHANGED
|
@@ -49,17 +49,23 @@ module Hutch
|
|
|
49
49
|
# RabbitMQ Exchange to use for publishing
|
|
50
50
|
string_setting :mq_exchange, 'hutch'
|
|
51
51
|
|
|
52
|
+
# RabbitMQ Exchange type to use for publishing
|
|
53
|
+
string_setting :mq_exchange_type, 'topic'
|
|
54
|
+
|
|
52
55
|
# RabbitMQ vhost to use
|
|
53
56
|
string_setting :mq_vhost, '/'
|
|
54
57
|
|
|
55
58
|
# RabbitMQ username to use.
|
|
56
59
|
#
|
|
57
|
-
#
|
|
60
|
+
# The <tt>guest</tt> user can only connect from localhost.
|
|
58
61
|
string_setting :mq_username, 'guest'
|
|
59
62
|
|
|
60
63
|
# RabbitMQ password
|
|
61
64
|
string_setting :mq_password, 'guest'
|
|
62
65
|
|
|
66
|
+
# RabbitMQ Auth Mechanism
|
|
67
|
+
string_setting :mq_auth_mechanism, 'PLAIN'
|
|
68
|
+
|
|
63
69
|
# RabbitMQ URI (takes precedence over MQ username, password, host, port and vhost settings)
|
|
64
70
|
string_setting :uri, nil
|
|
65
71
|
|
|
@@ -72,7 +78,7 @@ module Hutch
|
|
|
72
78
|
# RabbitMQ HTTP API port
|
|
73
79
|
number_setting :mq_api_port, 15672
|
|
74
80
|
|
|
75
|
-
# [RabbitMQ heartbeat timeout](
|
|
81
|
+
# [RabbitMQ heartbeat timeout](https://www.rabbitmq.com/docs/heartbeats)
|
|
76
82
|
number_setting :heartbeat, 30
|
|
77
83
|
|
|
78
84
|
# The <tt>basic.qos</tt> prefetch value to use.
|
|
@@ -80,6 +86,9 @@ module Hutch
|
|
|
80
86
|
# Default: `0`, no limit. See Bunny and RabbitMQ documentation.
|
|
81
87
|
number_setting :channel_prefetch, 0
|
|
82
88
|
|
|
89
|
+
# [Client-Provided Connection Name](https://www.rabbitmq.com/docs/connections#client-provided-names)
|
|
90
|
+
string_setting :connection_name, nil
|
|
91
|
+
|
|
83
92
|
# Bunny's socket open timeout
|
|
84
93
|
number_setting :connection_timeout, 11
|
|
85
94
|
|
|
@@ -89,7 +98,13 @@ module Hutch
|
|
|
89
98
|
# Bunny's socket write timeout
|
|
90
99
|
number_setting :write_timeout, 11
|
|
91
100
|
|
|
92
|
-
#
|
|
101
|
+
# Bunny's enable/disable network recovery
|
|
102
|
+
boolean_setting :automatically_recover, true
|
|
103
|
+
|
|
104
|
+
# Bunny's reconnect interval
|
|
105
|
+
number_setting :network_recovery_interval, 1
|
|
106
|
+
|
|
107
|
+
# Timeout (in seconds) for consumer threads to finish before being killed during graceful shutdown
|
|
93
108
|
number_setting :graceful_exit_timeout, 11
|
|
94
109
|
|
|
95
110
|
# Bunny consumer work pool size
|
|
@@ -136,6 +151,11 @@ module Hutch
|
|
|
136
151
|
# Prefix displayed on the consumers tags.
|
|
137
152
|
string_setting :consumer_tag_prefix, 'hutch'
|
|
138
153
|
|
|
154
|
+
# A namespace to help group your queues
|
|
155
|
+
string_setting :namespace, nil
|
|
156
|
+
|
|
157
|
+
string_setting :group, ''
|
|
158
|
+
|
|
139
159
|
# Set of all setting keys
|
|
140
160
|
ALL_KEYS = @boolean_keys + @number_keys + @string_keys
|
|
141
161
|
|
|
@@ -154,6 +174,7 @@ module Hutch
|
|
|
154
174
|
# @return [Hash]
|
|
155
175
|
def self.default_config
|
|
156
176
|
@settings_defaults.merge({
|
|
177
|
+
mq_client_properties: {},
|
|
157
178
|
mq_exchange_options: {},
|
|
158
179
|
mq_tls_cert: nil,
|
|
159
180
|
mq_tls_key: nil,
|
|
@@ -167,6 +188,7 @@ module Hutch
|
|
|
167
188
|
# that will fall back to "nack unconditionally"
|
|
168
189
|
error_acknowledgements: [],
|
|
169
190
|
setup_procs: [],
|
|
191
|
+
consumer_groups: {},
|
|
170
192
|
tracer: Hutch::Tracers::NullTracer,
|
|
171
193
|
namespace: nil,
|
|
172
194
|
pidfile: nil,
|
|
@@ -181,14 +203,7 @@ module Hutch
|
|
|
181
203
|
env_keys_configured.each_with_object({}) {|attr, result|
|
|
182
204
|
value = ENV[key_for(attr)]
|
|
183
205
|
|
|
184
|
-
|
|
185
|
-
when is_bool(attr) || value == 'false'
|
|
186
|
-
result[attr] = to_bool(value)
|
|
187
|
-
when is_num(attr)
|
|
188
|
-
result[attr] = value.to_i
|
|
189
|
-
else
|
|
190
|
-
result[attr] = value
|
|
191
|
-
end
|
|
206
|
+
result[attr] = type_cast(attr, value)
|
|
192
207
|
}
|
|
193
208
|
end
|
|
194
209
|
|
|
@@ -214,7 +229,12 @@ module Hutch
|
|
|
214
229
|
end
|
|
215
230
|
|
|
216
231
|
def self.to_bool(value)
|
|
217
|
-
|
|
232
|
+
case value
|
|
233
|
+
when nil, false, '', /^(false|f|no|n|0)$/i
|
|
234
|
+
false
|
|
235
|
+
else
|
|
236
|
+
true
|
|
237
|
+
end
|
|
218
238
|
end
|
|
219
239
|
|
|
220
240
|
def self.is_num(attr)
|
|
@@ -223,7 +243,7 @@ module Hutch
|
|
|
223
243
|
|
|
224
244
|
def self.set(attr, value)
|
|
225
245
|
check_attr(attr.to_sym)
|
|
226
|
-
user_config[attr.to_sym] = value
|
|
246
|
+
user_config[attr.to_sym] = type_cast(attr, value)
|
|
227
247
|
end
|
|
228
248
|
|
|
229
249
|
class << self
|
|
@@ -260,6 +280,18 @@ module Hutch
|
|
|
260
280
|
end
|
|
261
281
|
end
|
|
262
282
|
|
|
283
|
+
def self.type_cast(attr, value)
|
|
284
|
+
case
|
|
285
|
+
when is_bool(attr) || value == 'false'
|
|
286
|
+
to_bool(value)
|
|
287
|
+
when is_num(attr)
|
|
288
|
+
value.to_i
|
|
289
|
+
else
|
|
290
|
+
value
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
private_class_method :type_cast
|
|
294
|
+
|
|
263
295
|
def self.define_methods
|
|
264
296
|
@config.keys.each do |key|
|
|
265
297
|
define_singleton_method(key) do
|
data/lib/hutch/consumer.rb
CHANGED
|
@@ -13,11 +13,17 @@ module Hutch
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def reject!
|
|
16
|
-
|
|
16
|
+
@message_rejected = true
|
|
17
|
+
broker.reject(delivery_info.delivery_tag, channel: delivery_info.channel)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def requeue!
|
|
20
|
-
|
|
21
|
+
@message_rejected = true
|
|
22
|
+
broker.requeue(delivery_info.delivery_tag, channel: delivery_info.channel)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def message_rejected?
|
|
26
|
+
!!@message_rejected
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def logger
|
|
@@ -29,18 +35,56 @@ module Hutch
|
|
|
29
35
|
# wants to subscribe to.
|
|
30
36
|
def consume(*routing_keys)
|
|
31
37
|
@routing_keys = self.routing_keys.union(routing_keys)
|
|
38
|
+
# these are opt-in
|
|
39
|
+
@queue_mode = nil
|
|
40
|
+
@queue_type = nil
|
|
32
41
|
end
|
|
33
42
|
|
|
43
|
+
attr_reader :queue_mode, :queue_type, :initial_group_size
|
|
44
|
+
|
|
34
45
|
# Explicitly set the queue name
|
|
35
46
|
def queue_name(name)
|
|
36
47
|
@queue_name = name
|
|
37
48
|
end
|
|
38
49
|
|
|
39
|
-
#
|
|
50
|
+
# Opt out of the namespace prefix for this consumer's queue
|
|
51
|
+
def without_namespace
|
|
52
|
+
@without_namespace = true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def without_namespace?
|
|
56
|
+
!!@without_namespace
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Explicitly set the queue mode to 'lazy'
|
|
60
|
+
def lazy_queue
|
|
61
|
+
@queue_mode = 'lazy'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Explicitly set the queue type to 'classic'
|
|
65
|
+
def classic_queue
|
|
66
|
+
@queue_type = 'classic'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Explicitly set the queue type to 'quorum'
|
|
70
|
+
# @param [Hash] options the options params related to quorum queue
|
|
71
|
+
# @option options [Integer] :initial_group_size Initial Replication Factor
|
|
72
|
+
def quorum_queue(options = {})
|
|
73
|
+
@queue_type = 'quorum'
|
|
74
|
+
@initial_group_size = options[:initial_group_size]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Configures an optional argument that will be passed when declaring the queue.
|
|
78
|
+
# Prefer using a policy to this DSL: https://www.rabbitmq.com/parameters.html#policies
|
|
40
79
|
def arguments(arguments = {})
|
|
41
80
|
@arguments = arguments
|
|
42
81
|
end
|
|
43
82
|
|
|
83
|
+
# Configures queue options that will be passed when declaring the queue.
|
|
84
|
+
def queue_options(options = {})
|
|
85
|
+
@queue_options = options
|
|
86
|
+
end
|
|
87
|
+
|
|
44
88
|
# Set custom serializer class, override global value
|
|
45
89
|
def serializer(name)
|
|
46
90
|
@serializer = name
|
|
@@ -58,7 +102,22 @@ module Hutch
|
|
|
58
102
|
|
|
59
103
|
# Returns consumer custom arguments.
|
|
60
104
|
def get_arguments
|
|
61
|
-
@arguments || {}
|
|
105
|
+
all_arguments = @arguments || {}
|
|
106
|
+
|
|
107
|
+
all_arguments['x-queue-mode'] = @queue_mode if @queue_mode
|
|
108
|
+
all_arguments['x-queue-type'] = @queue_type if @queue_type
|
|
109
|
+
all_arguments['x-quorum-initial-group-size'] = @initial_group_size if @initial_group_size
|
|
110
|
+
|
|
111
|
+
all_arguments
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def get_options
|
|
115
|
+
default_options = { durable: true }
|
|
116
|
+
|
|
117
|
+
all_options = default_options.merge(@queue_options || {})
|
|
118
|
+
all_options[:arguments] = get_arguments
|
|
119
|
+
|
|
120
|
+
all_options
|
|
62
121
|
end
|
|
63
122
|
|
|
64
123
|
# Accessor for the consumer's routing key.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
require 'hutch/logging'
|
|
2
2
|
require 'airbrake'
|
|
3
|
+
require 'hutch/error_handlers/base'
|
|
3
4
|
|
|
4
5
|
module Hutch
|
|
5
6
|
module ErrorHandlers
|
|
6
|
-
class Airbrake
|
|
7
|
-
include Logging
|
|
7
|
+
class Airbrake < Base
|
|
8
8
|
|
|
9
9
|
def handle(properties, payload, consumer, ex)
|
|
10
10
|
message_id = properties.message_id
|
|
@@ -31,6 +31,24 @@ module Hutch
|
|
|
31
31
|
})
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
def handle_setup_exception(ex)
|
|
36
|
+
logger.error "Logging setup exception to Airbrake"
|
|
37
|
+
logger.error "#{ex.class} - #{ex.message}"
|
|
38
|
+
|
|
39
|
+
if ::Airbrake.respond_to?(:notify_or_ignore)
|
|
40
|
+
::Airbrake.notify_or_ignore(ex, {
|
|
41
|
+
error_class: ex.class.name,
|
|
42
|
+
error_message: "#{ ex.class.name }: #{ ex.message }",
|
|
43
|
+
backtrace: ex.backtrace,
|
|
44
|
+
cgi_data: ENV.to_hash,
|
|
45
|
+
})
|
|
46
|
+
else
|
|
47
|
+
::Airbrake.notify(ex, {
|
|
48
|
+
cgi_data: ENV.to_hash,
|
|
49
|
+
})
|
|
50
|
+
end
|
|
51
|
+
end
|
|
34
52
|
end
|
|
35
53
|
end
|
|
36
54
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Hutch
|
|
2
|
+
module ErrorHandlers
|
|
3
|
+
class Base
|
|
4
|
+
include Logging
|
|
5
|
+
|
|
6
|
+
def handle(properties, payload, consumer, ex)
|
|
7
|
+
raise NotImplementedError.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def handle_setup_exception(ex)
|
|
11
|
+
raise NotImplementedError.new
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "hutch/logging"
|
|
2
|
+
require "bugsnag"
|
|
3
|
+
require "hutch/error_handlers/base"
|
|
4
|
+
|
|
5
|
+
module Hutch
|
|
6
|
+
module ErrorHandlers
|
|
7
|
+
class Bugsnag < Base
|
|
8
|
+
def handle(properties, payload, consumer, ex)
|
|
9
|
+
message_id = properties.message_id
|
|
10
|
+
prefix = "message(#{message_id || "-"}):"
|
|
11
|
+
logger.error "#{prefix} Logging event to Bugsnag"
|
|
12
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
|
13
|
+
|
|
14
|
+
::Bugsnag.notify(ex) do |report|
|
|
15
|
+
report.add_tab(:hutch, {
|
|
16
|
+
payload: payload,
|
|
17
|
+
consumer: consumer
|
|
18
|
+
})
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def handle_setup_exception(ex)
|
|
23
|
+
logger.error "Logging setup exception to Bugsnag"
|
|
24
|
+
logger.error "#{ex.class} - #{ex.message}"
|
|
25
|
+
|
|
26
|
+
::Bugsnag.notify(ex)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|