hutch 0.18.0 → 1.1.0

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.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +20 -8
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +466 -2
  7. data/Gemfile +18 -4
  8. data/Guardfile +13 -4
  9. data/LICENSE +2 -1
  10. data/README.md +397 -32
  11. data/Rakefile +8 -1
  12. data/bin/ci/before_build.sh +20 -0
  13. data/bin/ci/install_on_debian.sh +46 -0
  14. data/hutch.gemspec +6 -7
  15. data/lib/hutch/acknowledgements/base.rb +16 -0
  16. data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
  17. data/lib/hutch/adapters/march_hare.rb +1 -1
  18. data/lib/hutch/broker.rb +127 -103
  19. data/lib/hutch/cli.rb +66 -25
  20. data/lib/hutch/config.rb +230 -55
  21. data/lib/hutch/consumer.rb +42 -3
  22. data/lib/hutch/error_handlers/airbrake.rb +44 -16
  23. data/lib/hutch/error_handlers/base.rb +15 -0
  24. data/lib/hutch/error_handlers/bugsnag.rb +30 -0
  25. data/lib/hutch/error_handlers/honeybadger.rb +33 -18
  26. data/lib/hutch/error_handlers/logger.rb +12 -6
  27. data/lib/hutch/error_handlers/rollbar.rb +28 -0
  28. data/lib/hutch/error_handlers/sentry.rb +15 -12
  29. data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
  30. data/lib/hutch/error_handlers.rb +3 -0
  31. data/lib/hutch/exceptions.rb +8 -1
  32. data/lib/hutch/logging.rb +5 -5
  33. data/lib/hutch/message.rb +2 -4
  34. data/lib/hutch/publisher.rb +75 -0
  35. data/lib/hutch/serializers/identity.rb +19 -0
  36. data/lib/hutch/serializers/json.rb +22 -0
  37. data/lib/hutch/tracers/datadog.rb +17 -0
  38. data/lib/hutch/tracers.rb +1 -0
  39. data/lib/hutch/version.rb +1 -2
  40. data/lib/hutch/waiter.rb +104 -0
  41. data/lib/hutch/worker.rb +81 -75
  42. data/lib/hutch.rb +15 -6
  43. data/lib/yard-settings/handler.rb +38 -0
  44. data/lib/yard-settings/yard-settings.rb +2 -0
  45. data/spec/hutch/broker_spec.rb +162 -77
  46. data/spec/hutch/cli_spec.rb +16 -3
  47. data/spec/hutch/config_spec.rb +121 -22
  48. data/spec/hutch/consumer_spec.rb +82 -4
  49. data/spec/hutch/error_handlers/airbrake_spec.rb +25 -10
  50. data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
  51. data/spec/hutch/error_handlers/honeybadger_spec.rb +24 -2
  52. data/spec/hutch/error_handlers/logger_spec.rb +14 -1
  53. data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
  54. data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
  55. data/spec/hutch/error_handlers/sentry_spec.rb +21 -2
  56. data/spec/hutch/logger_spec.rb +12 -6
  57. data/spec/hutch/message_spec.rb +2 -2
  58. data/spec/hutch/serializers/json_spec.rb +17 -0
  59. data/spec/hutch/tracers/datadog_spec.rb +44 -0
  60. data/spec/hutch/waiter_spec.rb +51 -0
  61. data/spec/hutch/worker_spec.rb +89 -5
  62. data/spec/spec_helper.rb +7 -5
  63. data/templates/default/class/html/settings.erb +0 -0
  64. data/templates/default/class/setup.rb +4 -0
  65. data/templates/default/fulldoc/html/css/hutch.css +13 -0
  66. data/templates/default/layout/html/setup.rb +7 -0
  67. data/templates/default/method_details/html/settings.erb +5 -0
  68. data/templates/default/method_details/setup.rb +4 -0
  69. data/templates/default/method_details/text/settings.erb +0 -0
  70. data/templates/default/module/html/settings.erb +40 -0
  71. data/templates/default/module/setup.rb +4 -0
  72. metadata +62 -43
  73. data/circle.yml +0 -3
@@ -25,7 +25,7 @@ module Hutch
25
25
  ch.prefetch = prefetch if prefetch
26
26
  end
27
27
 
28
- def create_channel(n = nil, consumer_pool_size = 1)
28
+ def create_channel(n = nil, consumer_pool_size = 1, consumer_pool_abort_on_exception = false)
29
29
  @connection.create_channel(n)
30
30
  end
31
31
 
data/lib/hutch/broker.rb CHANGED
@@ -1,7 +1,9 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
1
3
  require 'carrot-top'
2
- require 'securerandom'
3
4
  require 'hutch/logging'
4
5
  require 'hutch/exceptions'
6
+ require 'hutch/publisher'
5
7
 
6
8
  module Hutch
7
9
  class Broker
@@ -9,10 +11,38 @@ module Hutch
9
11
 
10
12
  attr_accessor :connection, :channel, :exchange, :api_client
11
13
 
14
+
15
+ DEFAULT_AMQP_PORT =
16
+ case RUBY_ENGINE
17
+ when "jruby" then
18
+ com.rabbitmq.client.ConnectionFactory::DEFAULT_AMQP_PORT
19
+ when "ruby" then
20
+ AMQ::Protocol::DEFAULT_PORT
21
+ end
22
+
23
+ DEFAULT_AMQPS_PORT =
24
+ case RUBY_ENGINE
25
+ when "jruby" then
26
+ com.rabbitmq.client.ConnectionFactory::DEFAULT_AMQP_OVER_SSL_PORT
27
+ when "ruby" then
28
+ AMQ::Protocol::TLS_PORT
29
+ end
30
+
31
+
32
+ # @param config [nil,Hash] Configuration override
12
33
  def initialize(config = nil)
13
34
  @config = config || Hutch::Config
14
35
  end
15
36
 
37
+ # Connect to broker
38
+ #
39
+ # @example
40
+ # Hutch::Broker.new.connect(enable_http_api_use: true) do
41
+ # # will disconnect after this block
42
+ # end
43
+ #
44
+ # @param [Hash] options The options to connect with
45
+ # @option options [Boolean] :enable_http_api_use
16
46
  def connect(options = {})
17
47
  @options = options
18
48
  set_up_amqp_connection
@@ -41,41 +71,44 @@ module Hutch
41
71
  def disconnect
42
72
  @channel.close if @channel
43
73
  @connection.close if @connection
44
- @channel, @connection, @exchange, @api_client = nil, nil, nil, nil
74
+ @channel = nil
75
+ @connection = nil
76
+ @exchange = nil
77
+ @api_client = nil
45
78
  end
46
79
 
47
- # Connect to RabbitMQ via AMQP. This sets up the main connection and
48
- # channel we use for talking to RabbitMQ. It also ensures the existance of
49
- # the exchange we'll be using.
80
+ # Connect to RabbitMQ via AMQP
81
+ #
82
+ # This sets up the main connection and channel we use for talking to
83
+ # RabbitMQ. It also ensures the existence of the exchange we'll be using.
50
84
  def set_up_amqp_connection
51
85
  open_connection!
52
86
  open_channel!
53
-
54
- exchange_name = @config[:mq_exchange]
55
- logger.info "using topic exchange '#{exchange_name}'"
56
-
57
- with_bunny_precondition_handler('exchange') do
58
- @exchange = @channel.topic(exchange_name, durable: true)
59
- end
87
+ declare_exchange!
88
+ declare_publisher!
60
89
  end
61
90
 
62
- def open_connection!
91
+ def open_connection
63
92
  logger.info "connecting to rabbitmq (#{sanitized_uri})"
64
93
 
65
- @connection = Hutch::Adapter.new(connection_params)
94
+ connection = Hutch::Adapter.new(connection_params)
66
95
 
67
96
  with_bunny_connection_handler(sanitized_uri) do
68
- @connection.start
97
+ connection.start
69
98
  end
70
99
 
71
100
  logger.info "connected to RabbitMQ at #{connection_params[:host]} as #{connection_params[:username]}"
72
- @connection
101
+ connection
73
102
  end
74
103
 
75
- def open_channel!
76
- logger.info "opening rabbitmq channel with pool size #{consumer_pool_size}"
77
- @channel = @connection.create_channel(nil, consumer_pool_size).tap do |ch|
78
- @connection.prefetch_channel(ch, @config[:channel_prefetch])
104
+ def open_connection!
105
+ @connection = open_connection
106
+ end
107
+
108
+ def open_channel
109
+ logger.info "opening rabbitmq channel with pool size #{consumer_pool_size}, abort on exception #{consumer_pool_abort_on_exception}"
110
+ connection.create_channel(nil, consumer_pool_size, consumer_pool_abort_on_exception).tap do |ch|
111
+ connection.prefetch_channel(ch, @config[:channel_prefetch])
79
112
  if @config[:publisher_confirms] || @config[:force_publisher_confirms]
80
113
  logger.info 'enabling publisher confirms'
81
114
  ch.confirm_select
@@ -83,6 +116,29 @@ module Hutch
83
116
  end
84
117
  end
85
118
 
119
+ def open_channel!
120
+ @channel = open_channel
121
+ end
122
+
123
+ def declare_exchange(ch = channel)
124
+ exchange_name = @config[:mq_exchange]
125
+ exchange_type = @config[:mq_exchange_type]
126
+ exchange_options = { durable: true }.merge(@config[:mq_exchange_options])
127
+ logger.info "using topic exchange '#{exchange_name}'"
128
+
129
+ with_bunny_precondition_handler('exchange') do
130
+ Bunny::Exchange.new(ch, exchange_type, exchange_name, exchange_options)
131
+ end
132
+ end
133
+
134
+ def declare_exchange!(*args)
135
+ @exchange = declare_exchange(*args)
136
+ end
137
+
138
+ def declare_publisher!
139
+ @publisher = Hutch::Publisher.new(connection, channel, exchange, @config)
140
+ end
141
+
86
142
  # Set up the connection to the RabbitMQ management API. Unfortunately, this
87
143
  # is necessary to do a few things that are impossible over AMQP. E.g.
88
144
  # listing queues and bindings.
@@ -118,7 +174,7 @@ module Hutch
118
174
  def queue(name, arguments = {})
119
175
  with_bunny_precondition_handler('queue') do
120
176
  namespace = @config[:namespace].to_s.downcase.gsub(/[^-_:\.\w]/, "")
121
- name = name.prepend(namespace + ":") unless namespace.empty?
177
+ name = name.prepend(namespace + ":") if namespace.present?
122
178
  channel.queue(name, durable: true, arguments: arguments)
123
179
  end
124
180
  end
@@ -126,46 +182,44 @@ module Hutch
126
182
  # Return a mapping of queue names to the routing keys they're bound to.
127
183
  def bindings
128
184
  results = Hash.new { |hash, key| hash[key] = [] }
129
- @api_client.bindings.each do |binding|
130
- next if binding['destination'] == binding['routing_key']
131
- next unless binding['source'] == @config[:mq_exchange]
132
- next unless binding['vhost'] == @config[:mq_vhost]
185
+
186
+ filtered = api_client.bindings.
187
+ reject { |b| b['destination'] == b['routing_key'] }.
188
+ select { |b| b['source'] == @config[:mq_exchange] && b['vhost'] == @config[:mq_vhost] }
189
+
190
+ filtered.each do |binding|
133
191
  results[binding['destination']] << binding['routing_key']
134
192
  end
193
+
135
194
  results
136
195
  end
137
196
 
197
+ # Find the existing bindings, and unbind any redundant bindings
198
+ def unbind_redundant_bindings(queue, routing_keys)
199
+ return unless http_api_use_enabled?
200
+
201
+ filtered = bindings.select { |dest, keys| dest == queue.name }
202
+ filtered.each do |dest, keys|
203
+ keys.reject { |key| routing_keys.include?(key) }.each do |key|
204
+ logger.debug "removing redundant binding #{queue.name} <--> #{key}"
205
+ queue.unbind(exchange, routing_key: key)
206
+ end
207
+ end
208
+ end
209
+
138
210
  # Bind a queue to the broker's exchange on the routing keys provided. Any
139
211
  # existing bindings on the queue that aren't present in the array of
140
212
  # routing keys will be unbound.
141
213
  def bind_queue(queue, routing_keys)
142
- if http_api_use_enabled?
143
- # Find the existing bindings, and unbind any redundant bindings
144
- queue_bindings = bindings.select { |dest, keys| dest == queue.name }
145
- queue_bindings.each do |dest, keys|
146
- keys.reject { |key| routing_keys.include?(key) }.each do |key|
147
- logger.debug "removing redundant binding #{queue.name} <--> #{key}"
148
- queue.unbind(@exchange, routing_key: key)
149
- end
150
- end
151
- end
214
+ unbind_redundant_bindings(queue, routing_keys)
152
215
 
153
216
  # Ensure all the desired bindings are present
154
217
  routing_keys.each do |routing_key|
155
218
  logger.debug "creating binding #{queue.name} <--> #{routing_key}"
156
- queue.bind(@exchange, routing_key: routing_key)
219
+ queue.bind(exchange, routing_key: routing_key)
157
220
  end
158
221
  end
159
222
 
160
- # Each subscriber is run in a thread. This calls Thread#join on each of the
161
- # subscriber threads.
162
- def wait_on_threads(timeout)
163
- # Thread#join returns nil when the timeout is hit. If any return nil,
164
- # the threads didn't all join so we return false.
165
- per_thread_timeout = timeout.to_f / work_pool_threads.length
166
- work_pool_threads.none? { |thread| thread.join(per_thread_timeout).nil? }
167
- end
168
-
169
223
  def stop
170
224
  if defined?(JRUBY_VERSION)
171
225
  channel.close
@@ -180,67 +234,40 @@ module Hutch
180
234
  end
181
235
 
182
236
  def requeue(delivery_tag)
183
- @channel.reject(delivery_tag, true)
237
+ channel.reject(delivery_tag, true)
184
238
  end
185
239
 
186
240
  def reject(delivery_tag, requeue=false)
187
- @channel.reject(delivery_tag, requeue)
241
+ channel.reject(delivery_tag, requeue)
188
242
  end
189
243
 
190
244
  def ack(delivery_tag)
191
- @channel.ack(delivery_tag, false)
245
+ channel.ack(delivery_tag, false)
192
246
  end
193
247
 
194
248
  def nack(delivery_tag)
195
- @channel.nack(delivery_tag, false, false)
249
+ channel.nack(delivery_tag, false, false)
196
250
  end
197
251
 
198
- def publish(routing_key, message, properties = {})
199
- ensure_connection!(routing_key, message)
200
-
201
- non_overridable_properties = {
202
- routing_key: routing_key,
203
- timestamp: @connection.current_timestamp,
204
- content_type: 'application/json'
205
- }
206
- properties[:message_id] ||= generate_id
207
-
208
- json = JSON.dump(message)
209
- logger.info("publishing message '#{json}' to #{routing_key}")
210
- response = @exchange.publish(json, {persistent: true}.
211
- merge(properties).
212
- merge(global_properties).
213
- merge(non_overridable_properties))
214
-
215
- channel.wait_for_confirms if @config[:force_publisher_confirms]
216
- response
252
+ def publish(*args)
253
+ @publisher.publish(*args)
217
254
  end
218
255
 
219
256
  def confirm_select(*args)
220
- @channel.confirm_select(*args)
257
+ channel.confirm_select(*args)
221
258
  end
222
259
 
223
260
  def wait_for_confirms
224
- @channel.wait_for_confirms
261
+ channel.wait_for_confirms
225
262
  end
226
263
 
264
+ # @return [Boolean] True if channel is set up to use publisher confirmations.
227
265
  def using_publisher_confirmations?
228
- @channel.using_publisher_confirmations?
266
+ channel.using_publisher_confirmations?
229
267
  end
230
268
 
231
269
  private
232
270
 
233
- def raise_publish_error(reason, routing_key, message)
234
- msg = "unable to publish - #{reason}. Message: #{JSON.dump(message)}, Routing key: #{routing_key}."
235
- logger.error(msg)
236
- raise PublishError, msg
237
- end
238
-
239
- def ensure_connection!(routing_key, message)
240
- raise_publish_error('no connection to broker', routing_key, message) unless @connection
241
- raise_publish_error('connection is closed', routing_key, message) unless @connection.open?
242
- end
243
-
244
271
  def api_config
245
272
  @api_config ||= OpenStruct.new.tap do |config|
246
273
  config.host = @config[:mq_api_host]
@@ -259,41 +286,46 @@ module Hutch
259
286
  {}.tap do |params|
260
287
  params[:host] = @config[:mq_host]
261
288
  params[:port] = @config[:mq_port]
262
- params[:vhost] = if @config[:mq_vhost] && "" != @config[:mq_vhost]
263
- @config[:mq_vhost]
264
- else
265
- Hutch::Adapter::DEFAULT_VHOST
266
- end
289
+ params[:vhost] = @config[:mq_vhost].presence || Hutch::Adapter::DEFAULT_VHOST
267
290
  params[:username] = @config[:mq_username]
268
291
  params[:password] = @config[:mq_password]
269
292
  params[:tls] = @config[:mq_tls]
270
293
  params[:tls_key] = @config[:mq_tls_key]
271
294
  params[:tls_cert] = @config[:mq_tls_cert]
295
+ params[:verify_peer] = @config[:mq_verify_peer]
296
+ if @config[:mq_tls_ca_certificates]
297
+ params[:tls_ca_certificates] = @config[:mq_tls_ca_certificates]
298
+ end
272
299
  params[:heartbeat] = @config[:heartbeat]
273
300
  params[:connection_timeout] = @config[:connection_timeout]
274
301
  params[:read_timeout] = @config[:read_timeout]
275
302
  params[:write_timeout] = @config[:write_timeout]
276
303
 
277
304
 
278
- params[:automatically_recover] = true
279
- params[:network_recovery_interval] = 1
305
+ params[:automatically_recover] = @config[:automatically_recover]
306
+ params[:network_recovery_interval] = @config[:network_recovery_interval]
280
307
 
281
- params[:client_logger] = @config[:client_logger] if @config[:client_logger]
308
+ params[:logger] = @config[:client_logger] if @config[:client_logger]
282
309
  end
283
310
  end
284
311
 
285
312
  def parse_uri
286
- return unless @config[:uri] && !@config[:uri].empty?
313
+ return if @config[:uri].blank?
287
314
 
288
315
  u = URI.parse(@config[:uri])
289
316
 
317
+ @config[:mq_tls] = u.scheme == 'amqps'
290
318
  @config[:mq_host] = u.host
291
- @config[:mq_port] = u.port
319
+ @config[:mq_port] = u.port || default_mq_port
292
320
  @config[:mq_vhost] = u.path.sub(/^\//, "")
293
321
  @config[:mq_username] = u.user
294
322
  @config[:mq_password] = u.password
295
323
  end
296
324
 
325
+ def default_mq_port
326
+ @config[:mq_tls] ? DEFAULT_AMQPS_PORT : DEFAULT_AMQP_PORT
327
+ end
328
+
297
329
  def sanitized_uri
298
330
  p = connection_params
299
331
  scheme = p[:tls] ? "amqps" : "amqp"
@@ -335,24 +367,16 @@ module Hutch
335
367
  raise ConnectionError.new("couldn't connect to rabbitmq at #{uri}. Check your configuration, network connectivity and RabbitMQ logs.")
336
368
  end
337
369
 
338
- def work_pool_threads
339
- channel_work_pool.threads || []
340
- end
341
-
342
370
  def channel_work_pool
343
- @channel.work_pool
371
+ channel.work_pool
344
372
  end
345
373
 
346
374
  def consumer_pool_size
347
375
  @config[:consumer_pool_size]
348
376
  end
349
377
 
350
- def generate_id
351
- SecureRandom.uuid
352
- end
353
-
354
- def global_properties
355
- Hutch.global_properties.respond_to?(:call) ? Hutch.global_properties.call : Hutch.global_properties
378
+ def consumer_pool_abort_on_exception
379
+ @config[:consumer_pool_abort_on_exception]
356
380
  end
357
381
  end
358
382
  end
data/lib/hutch/cli.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require 'optparse'
2
2
 
3
- require 'hutch/version'
4
3
  require 'hutch/logging'
5
- require 'hutch/exceptions'
6
4
  require 'hutch/config'
5
+ require 'hutch/version'
6
+ require 'hutch/exceptions'
7
7
 
8
8
  module Hutch
9
9
  class CLI
@@ -11,9 +11,10 @@ module Hutch
11
11
 
12
12
  # Run a Hutch worker with the command line interface.
13
13
  def run(argv = ARGV)
14
+ Hutch::Config.initialize
14
15
  parse_options(argv)
15
16
 
16
- ::Process.daemon(true) if Hutch::Config.daemonise
17
+ daemonise_process
17
18
 
18
19
  write_pid if Hutch::Config.pidfile
19
20
 
@@ -32,6 +33,23 @@ module Hutch
32
33
  def load_app
33
34
  # Try to load a Rails app in the current directory
34
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 Raven 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?(Raven)
43
+ Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::SentryRaven.new
44
+ end
45
+ if defined?(Sentry)
46
+ Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
47
+ end
48
+
49
+ true
50
+ end
51
+
52
+ def set_up_code_paths!
35
53
  Hutch::Config.require_paths.each do |path|
36
54
  # See if each path is a Rails app. If so, try to load it.
37
55
  next if load_rails_app(path)
@@ -42,34 +60,27 @@ module Hutch
42
60
  # Need to add '.' to load path for relative requires
43
61
  $LOAD_PATH << '.'
44
62
  require path
45
- rescue LoadError
46
- logger.fatal "could not load file '#{path}'"
63
+ rescue LoadError => e
64
+ logger.fatal "could not load file '#{path}': #{e}"
47
65
  return false
48
66
  ensure
49
67
  # Clean up load path
50
68
  $LOAD_PATH.pop
51
69
  end
52
70
  end
53
-
54
- # Because of the order things are required when we run the Hutch binary
55
- # in hutch/bin, the Sentry Raven gem gets required **after** the error
56
- # handlers are set up. Due to this, we never got any Sentry notifications
57
- # when an error occurred in any of the consumers.
58
- if defined?(Raven)
59
- Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
60
- end
61
-
62
- true
63
71
  end
64
72
 
65
73
  def load_rails_app(path)
66
74
  # path should point to the app's top level directory
67
75
  if File.directory?(path)
68
- # Smells like a Rails app if it's got a config/environment.rb file
76
+ # Smells like a Rails app if it's got a script/rails or bin/rails file
77
+ is_rails_app = ['script/rails', 'bin/rails'].any? do |file|
78
+ File.exist?(File.expand_path(File.join(path, file)))
79
+ end
69
80
  rails_path = File.expand_path(File.join(path, 'config/environment.rb'))
70
- if File.exists?(rails_path)
71
- logger.info "found rails project (#{path}), booting app"
81
+ if is_rails_app && File.exist?(rails_path)
72
82
  ENV['RACK_ENV'] ||= ENV['RAILS_ENV'] || 'development'
83
+ logger.info "found rails project (#{path}), booting app in #{ENV['RACK_ENV']} environment"
73
84
  require rails_path
74
85
  ::Rails.application.eager_load!
75
86
  return true
@@ -82,10 +93,13 @@ module Hutch
82
93
  # gracefully (with a SIGQUIT, SIGTERM or SIGINT).
83
94
  def start_work_loop
84
95
  Hutch.connect
85
- @worker = Hutch::Worker.new(Hutch.broker, Hutch.consumers)
96
+ @worker = Hutch::Worker.new(Hutch.broker, Hutch.consumers, Hutch::Config.setup_procs)
86
97
  @worker.run
87
98
  :success
88
99
  rescue ConnectionError, AuthenticationError, WorkerSetupError => ex
100
+ Hutch::Config[:error_handlers].each do |backend|
101
+ backend.handle_setup_exception(ex)
102
+ end
89
103
  logger.fatal ex.message
90
104
  :error
91
105
  end
@@ -106,14 +120,16 @@ module Hutch
106
120
  Hutch::Config.mq_tls = tls
107
121
  end
108
122
 
109
- opts.on('--mq-tls-cert FILE', 'Certificate for TLS client verification') do |file|
110
- abort "Certificate file '#{file}' not found" unless File.exists?(file)
111
- Hutch::Config.mq_tls_cert = file
123
+ opts.on('--mq-tls-cert FILE', 'Certificate for TLS client verification') do |file|
124
+ abort_without_file(file, 'Certificate file') do
125
+ Hutch::Config.mq_tls_cert = file
126
+ end
112
127
  end
113
128
 
114
129
  opts.on('--mq-tls-key FILE', 'Private key for TLS client verification') do |file|
115
- abort "Private key file '#{file}' not found" unless File.exists?(file)
116
- Hutch::Config.mq_tls_key = file
130
+ abort_without_file(file, 'Private key file') do
131
+ Hutch::Config.mq_tls_key = file
132
+ end
117
133
  end
118
134
 
119
135
  opts.on('--mq-exchange EXCHANGE',
@@ -151,7 +167,7 @@ module Hutch
151
167
  begin
152
168
  File.open(file) { |fp| Hutch::Config.load_from_file(fp) }
153
169
  rescue Errno::ENOENT
154
- abort "Config file '#{file}' not found"
170
+ abort_with_message("Config file '#{file}' not found")
155
171
  end
156
172
  end
157
173
 
@@ -183,6 +199,10 @@ module Hutch
183
199
  Hutch::Config.pidfile = pidfile
184
200
  end
185
201
 
202
+ opts.on('--only-group GROUP', 'Load only consumers in this group') do |group|
203
+ Hutch::Config.group = group
204
+ end
205
+
186
206
  opts.on('--version', 'Print the version and exit') do
187
207
  puts "hutch v#{VERSION}"
188
208
  exit 0
@@ -201,5 +221,26 @@ module Hutch
201
221
  File.open(pidfile, 'w') { |f| f.puts ::Process.pid }
202
222
  end
203
223
 
224
+ private
225
+
226
+ def daemonise_process
227
+ return unless Hutch::Config.daemonise
228
+ if defined?(JRUBY_VERSION)
229
+ Hutch.logger.warn "JRuby ignores the --daemonise option"
230
+ return
231
+ end
232
+
233
+ ::Process.daemon(true)
234
+ end
235
+
236
+ def abort_without_file(file, file_description, &block)
237
+ abort_with_message("#{file_description} '#{file}' not found") unless File.exists?(file)
238
+
239
+ yield
240
+ end
241
+
242
+ def abort_with_message(message)
243
+ abort message
244
+ end
204
245
  end
205
246
  end