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.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.travis.yml +20 -8
- data/.yardopts +5 -0
- data/CHANGELOG.md +466 -2
- data/Gemfile +18 -4
- data/Guardfile +13 -4
- data/LICENSE +2 -1
- data/README.md +397 -32
- data/Rakefile +8 -1
- data/bin/ci/before_build.sh +20 -0
- data/bin/ci/install_on_debian.sh +46 -0
- data/hutch.gemspec +6 -7
- data/lib/hutch/acknowledgements/base.rb +16 -0
- data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
- data/lib/hutch/adapters/march_hare.rb +1 -1
- data/lib/hutch/broker.rb +127 -103
- data/lib/hutch/cli.rb +66 -25
- data/lib/hutch/config.rb +230 -55
- data/lib/hutch/consumer.rb +42 -3
- data/lib/hutch/error_handlers/airbrake.rb +44 -16
- 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 +33 -18
- data/lib/hutch/error_handlers/logger.rb +12 -6
- data/lib/hutch/error_handlers/rollbar.rb +28 -0
- data/lib/hutch/error_handlers/sentry.rb +15 -12
- data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
- data/lib/hutch/error_handlers.rb +3 -0
- data/lib/hutch/exceptions.rb +8 -1
- data/lib/hutch/logging.rb +5 -5
- data/lib/hutch/message.rb +2 -4
- data/lib/hutch/publisher.rb +75 -0
- data/lib/hutch/serializers/identity.rb +19 -0
- data/lib/hutch/serializers/json.rb +22 -0
- data/lib/hutch/tracers/datadog.rb +17 -0
- data/lib/hutch/tracers.rb +1 -0
- data/lib/hutch/version.rb +1 -2
- data/lib/hutch/waiter.rb +104 -0
- data/lib/hutch/worker.rb +81 -75
- data/lib/hutch.rb +15 -6
- data/lib/yard-settings/handler.rb +38 -0
- data/lib/yard-settings/yard-settings.rb +2 -0
- data/spec/hutch/broker_spec.rb +162 -77
- data/spec/hutch/cli_spec.rb +16 -3
- data/spec/hutch/config_spec.rb +121 -22
- data/spec/hutch/consumer_spec.rb +82 -4
- data/spec/hutch/error_handlers/airbrake_spec.rb +25 -10
- data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +24 -2
- data/spec/hutch/error_handlers/logger_spec.rb +14 -1
- data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
- data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +21 -2
- data/spec/hutch/logger_spec.rb +12 -6
- data/spec/hutch/message_spec.rb +2 -2
- data/spec/hutch/serializers/json_spec.rb +17 -0
- data/spec/hutch/tracers/datadog_spec.rb +44 -0
- data/spec/hutch/waiter_spec.rb +51 -0
- data/spec/hutch/worker_spec.rb +89 -5
- data/spec/spec_helper.rb +7 -5
- data/templates/default/class/html/settings.erb +0 -0
- data/templates/default/class/setup.rb +4 -0
- data/templates/default/fulldoc/html/css/hutch.css +13 -0
- data/templates/default/layout/html/setup.rb +7 -0
- data/templates/default/method_details/html/settings.erb +5 -0
- data/templates/default/method_details/setup.rb +4 -0
- data/templates/default/method_details/text/settings.erb +0 -0
- data/templates/default/module/html/settings.erb +40 -0
- data/templates/default/module/setup.rb +4 -0
- metadata +62 -43
- data/circle.yml +0 -3
@@ -1,22 +1,25 @@
|
|
1
1
|
require 'hutch/logging'
|
2
|
-
require '
|
2
|
+
require 'sentry-ruby'
|
3
|
+
require 'hutch/error_handlers/base'
|
3
4
|
|
4
5
|
module Hutch
|
5
6
|
module ErrorHandlers
|
6
|
-
class Sentry
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
class Sentry < 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 Sentry"
|
12
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
13
|
+
::Sentry.configure_scope do |scope|
|
14
|
+
scope.set_context("payload", payload)
|
12
15
|
end
|
16
|
+
::Sentry.capture_exception(ex)
|
13
17
|
end
|
14
18
|
|
15
|
-
def
|
16
|
-
|
17
|
-
logger.error
|
18
|
-
|
19
|
-
Raven.capture_exception(ex)
|
19
|
+
def handle_setup_exception(ex)
|
20
|
+
logger.error "Logging setup exception to Sentry"
|
21
|
+
logger.error "#{ex.class} - #{ex.message}"
|
22
|
+
::Sentry.capture_exception(ex)
|
20
23
|
end
|
21
24
|
end
|
22
25
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'hutch/logging'
|
2
|
+
require 'raven'
|
3
|
+
require 'hutch/error_handlers/base'
|
4
|
+
|
5
|
+
module Hutch
|
6
|
+
module ErrorHandlers
|
7
|
+
class SentryRaven < Base
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
unless Raven.respond_to?(:capture_exception)
|
11
|
+
raise "The Hutch Sentry error handler requires Raven >= 0.4.0"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle(properties, payload, consumer, ex)
|
16
|
+
message_id = properties.message_id
|
17
|
+
prefix = "message(#{message_id || '-'}):"
|
18
|
+
logger.error "#{prefix} Logging event to Sentry"
|
19
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
20
|
+
Raven.capture_exception(ex, extra: { payload: payload })
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_setup_exception(ex)
|
24
|
+
logger.error "Logging setup exception to Sentry"
|
25
|
+
logger.error "#{ex.class} - #{ex.message}"
|
26
|
+
Raven.capture_exception(ex)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/hutch/error_handlers.rb
CHANGED
@@ -2,7 +2,10 @@ module Hutch
|
|
2
2
|
module ErrorHandlers
|
3
3
|
autoload :Logger, 'hutch/error_handlers/logger'
|
4
4
|
autoload :Sentry, 'hutch/error_handlers/sentry'
|
5
|
+
autoload :SentryRaven, 'hutch/error_handlers/sentry_raven'
|
5
6
|
autoload :Honeybadger, 'hutch/error_handlers/honeybadger'
|
6
7
|
autoload :Airbrake, 'hutch/error_handlers/airbrake'
|
8
|
+
autoload :Rollbar, 'hutch/error_handlers/rollbar'
|
9
|
+
autoload :Bugsnag, 'hutch/error_handlers/bugsnag'
|
7
10
|
end
|
8
11
|
end
|
data/lib/hutch/exceptions.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Hutch
|
2
|
-
|
2
|
+
if defined?(JRUBY_VERSION)
|
3
|
+
require 'march_hare/exceptions'
|
4
|
+
class Exception < MarchHare::Exception; end
|
5
|
+
else
|
6
|
+
require "bunny/exceptions"
|
7
|
+
# Bunny::Exception inherits from StandardError
|
8
|
+
class Exception < Bunny::Exception; end
|
9
|
+
end
|
3
10
|
class ConnectionError < Exception; end
|
4
11
|
class AuthenticationError < Exception; end
|
5
12
|
class WorkerSetupError < Exception; end
|
data/lib/hutch/logging.rb
CHANGED
@@ -9,12 +9,12 @@ module Hutch
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.setup_logger
|
12
|
+
def self.setup_logger
|
13
13
|
require 'hutch/config'
|
14
|
-
@logger = Logger.new(
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
@logger = Logger.new($stdout).tap do |l|
|
15
|
+
l.level = Hutch::Config.log_level
|
16
|
+
l.formatter = HutchFormatter.new
|
17
|
+
end
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.logger
|
data/lib/hutch/message.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
require 'multi_json'
|
2
1
|
require 'forwardable'
|
3
|
-
require 'active_support/core_ext/hash/indifferent_access'
|
4
2
|
|
5
3
|
module Hutch
|
6
4
|
class Message
|
@@ -8,11 +6,11 @@ module Hutch
|
|
8
6
|
|
9
7
|
attr_reader :delivery_info, :properties, :payload
|
10
8
|
|
11
|
-
def initialize(delivery_info, properties, payload)
|
9
|
+
def initialize(delivery_info, properties, payload, serializer)
|
12
10
|
@delivery_info = delivery_info
|
13
11
|
@properties = properties
|
14
12
|
@payload = payload
|
15
|
-
@body =
|
13
|
+
@body = serializer.decode(payload)
|
16
14
|
end
|
17
15
|
|
18
16
|
def_delegator :@body, :[]
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'hutch/logging'
|
3
|
+
require 'hutch/exceptions'
|
4
|
+
|
5
|
+
module Hutch
|
6
|
+
class Publisher
|
7
|
+
include Logging
|
8
|
+
attr_reader :connection, :channel, :exchange, :config
|
9
|
+
|
10
|
+
def initialize(connection, channel, exchange, config = Hutch::Config)
|
11
|
+
@connection = connection
|
12
|
+
@channel = channel
|
13
|
+
@exchange = exchange
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
def publish(routing_key, message, properties = {}, options = {})
|
18
|
+
ensure_connection!(routing_key, message)
|
19
|
+
|
20
|
+
serializer = options[:serializer] || config[:serializer]
|
21
|
+
|
22
|
+
non_overridable_properties = {
|
23
|
+
routing_key: routing_key,
|
24
|
+
timestamp: connection.current_timestamp,
|
25
|
+
content_type: serializer.content_type,
|
26
|
+
}
|
27
|
+
properties[:message_id] ||= generate_id
|
28
|
+
|
29
|
+
payload = serializer.encode(message)
|
30
|
+
|
31
|
+
log_publication(serializer, payload, routing_key)
|
32
|
+
|
33
|
+
response = exchange.publish(payload, {persistent: true}.
|
34
|
+
merge(properties).
|
35
|
+
merge(global_properties).
|
36
|
+
merge(non_overridable_properties))
|
37
|
+
|
38
|
+
channel.wait_for_confirms if config[:force_publisher_confirms]
|
39
|
+
response
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def log_publication(serializer, payload, routing_key)
|
45
|
+
logger.debug {
|
46
|
+
spec =
|
47
|
+
if serializer.binary?
|
48
|
+
"#{payload.bytesize} bytes message"
|
49
|
+
else
|
50
|
+
"message '#{payload}'"
|
51
|
+
end
|
52
|
+
"publishing #{spec} to #{routing_key}"
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def raise_publish_error(reason, routing_key, message)
|
57
|
+
msg = "unable to publish - #{reason}. Message: #{JSON.dump(message)}, Routing key: #{routing_key}."
|
58
|
+
logger.error(msg)
|
59
|
+
raise PublishError, msg
|
60
|
+
end
|
61
|
+
|
62
|
+
def ensure_connection!(routing_key, message)
|
63
|
+
raise_publish_error('no connection to broker', routing_key, message) unless connection
|
64
|
+
raise_publish_error('connection is closed', routing_key, message) unless connection.open?
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_id
|
68
|
+
SecureRandom.uuid
|
69
|
+
end
|
70
|
+
|
71
|
+
def global_properties
|
72
|
+
Hutch.global_properties.respond_to?(:call) ? Hutch.global_properties.call : Hutch.global_properties
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Hutch
|
2
|
+
module Serializers
|
3
|
+
class Identity
|
4
|
+
|
5
|
+
def self.encode(payload)
|
6
|
+
payload
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.decode(payload)
|
10
|
+
payload
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.binary? ; false ; end
|
14
|
+
|
15
|
+
def self.content_type ; nil ; end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
|
4
|
+
module Hutch
|
5
|
+
module Serializers
|
6
|
+
class JSON
|
7
|
+
|
8
|
+
def self.encode(payload)
|
9
|
+
::MultiJson.dump(payload)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.decode(payload)
|
13
|
+
::MultiJson.load(payload).with_indifferent_access
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.binary? ; false ; end
|
17
|
+
|
18
|
+
def self.content_type ; 'application/json' ; end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'ddtrace'
|
2
|
+
|
3
|
+
module Hutch
|
4
|
+
module Tracers
|
5
|
+
class Datadog
|
6
|
+
def initialize(klass)
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle(message)
|
11
|
+
::Datadog.tracer.trace(@klass.class.name, service: 'hutch', span_type: 'rabbitmq') do
|
12
|
+
@klass.process(message)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/hutch/tracers.rb
CHANGED
data/lib/hutch/version.rb
CHANGED
data/lib/hutch/waiter.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'hutch/logging'
|
2
|
+
|
3
|
+
module Hutch
|
4
|
+
# Signal-handling class.
|
5
|
+
#
|
6
|
+
# Currently, the signal USR2 performs a thread dump,
|
7
|
+
# while QUIT, TERM and INT all perform a graceful shutdown.
|
8
|
+
class Waiter
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
class ContinueProcessingSignals < RuntimeError
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.supported_signals_of(list)
|
15
|
+
list.keep_if { |s| Signal.list.keys.include?(s) }.tap do |result|
|
16
|
+
result.delete('QUIT') if defined?(JRUBY_VERSION)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
SHUTDOWN_SIGNALS = supported_signals_of(%w(QUIT TERM INT)).freeze
|
21
|
+
# We have chosen a JRuby-supported signal
|
22
|
+
USER_SIGNALS = supported_signals_of(%w(USR2)).freeze
|
23
|
+
REGISTERED_SIGNALS = (SHUTDOWN_SIGNALS + USER_SIGNALS).freeze
|
24
|
+
|
25
|
+
def self.wait_until_signaled
|
26
|
+
new.wait_until_signaled
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait_until_signaled
|
30
|
+
self.sig_read, self.sig_write = IO.pipe
|
31
|
+
|
32
|
+
register_signal_handlers
|
33
|
+
|
34
|
+
begin
|
35
|
+
wait_for_signal
|
36
|
+
|
37
|
+
sig = sig_read.gets.strip
|
38
|
+
handle_signal(sig)
|
39
|
+
rescue ContinueProcessingSignals
|
40
|
+
retry
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle_signal(sig)
|
45
|
+
raise ContinueProcessingSignals unless REGISTERED_SIGNALS.include?(sig)
|
46
|
+
if user_signal?(sig)
|
47
|
+
handle_user_signal(sig)
|
48
|
+
else
|
49
|
+
handle_shutdown_signal(sig)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @raise ContinueProcessingSignals
|
54
|
+
def handle_user_signal(sig)
|
55
|
+
case sig
|
56
|
+
when 'USR2' then log_thread_backtraces
|
57
|
+
else raise "Assertion failed - unhandled signal: #{sig.inspect}"
|
58
|
+
end
|
59
|
+
raise ContinueProcessingSignals
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_shutdown_signal(sig)
|
63
|
+
logger.info "caught SIG#{sig}, stopping hutch..."
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def log_thread_backtraces
|
69
|
+
logger.info 'Requested a VM-wide thread stack trace dump...'
|
70
|
+
Thread.list.each do |thread|
|
71
|
+
logger.info "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
72
|
+
logger.info backtrace_for(thread)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def backtrace_for(thread)
|
77
|
+
if thread.backtrace
|
78
|
+
thread.backtrace.join("\n")
|
79
|
+
else
|
80
|
+
'<no backtrace available>'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_accessor :sig_read, :sig_write
|
85
|
+
|
86
|
+
def wait_for_signal
|
87
|
+
IO.select([sig_read])
|
88
|
+
end
|
89
|
+
|
90
|
+
def register_signal_handlers
|
91
|
+
REGISTERED_SIGNALS.each do |sig|
|
92
|
+
# This needs to be reentrant, so we queue up signals to be handled
|
93
|
+
# in the run loop, rather than acting on signals here
|
94
|
+
trap(sig) do
|
95
|
+
sig_write.puts(sig)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def user_signal?(sig)
|
101
|
+
USER_SIGNALS.include?(sig)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/hutch/worker.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
require 'hutch/message'
|
2
2
|
require 'hutch/logging'
|
3
3
|
require 'hutch/broker'
|
4
|
+
require 'hutch/acknowledgements/nack_on_all_failures'
|
5
|
+
require 'hutch/waiter'
|
4
6
|
require 'carrot-top'
|
7
|
+
require 'securerandom'
|
5
8
|
|
6
9
|
module Hutch
|
7
10
|
class Worker
|
8
11
|
include Logging
|
9
12
|
|
10
|
-
def initialize(broker, consumers)
|
13
|
+
def initialize(broker, consumers, setup_procs)
|
11
14
|
@broker = broker
|
12
15
|
self.consumers = consumers
|
16
|
+
self.setup_procs = setup_procs
|
13
17
|
end
|
14
18
|
|
15
19
|
# Run the main event loop. The consumers will be set up with queues, and
|
@@ -17,46 +21,11 @@ module Hutch
|
|
17
21
|
# never returns.
|
18
22
|
def run
|
19
23
|
setup_queues
|
24
|
+
setup_procs.each(&:call)
|
20
25
|
|
21
|
-
|
22
|
-
register_signal_handlers
|
26
|
+
Waiter.wait_until_signaled
|
23
27
|
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
def main_loop
|
28
|
-
if defined?(JRUBY_VERSION)
|
29
|
-
# Binds shutdown listener to notify main thread if channel was closed
|
30
|
-
bind_shutdown_handler
|
31
|
-
|
32
|
-
handle_signals until shutdown_not_called?(0.1)
|
33
|
-
else
|
34
|
-
# Take a break from Thread#join every 0.1 seconds to check if we've
|
35
|
-
# been sent any signals
|
36
|
-
handle_signals until @broker.wait_on_threads(0.1)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Register handlers for SIG{QUIT,TERM,INT} to shut down the worker
|
41
|
-
# gracefully. Forceful shutdowns are very bad!
|
42
|
-
def register_signal_handlers
|
43
|
-
Thread.main[:signal_queue] = []
|
44
|
-
%w(QUIT TERM INT).keep_if { |s| Signal.list.keys.include? s }.map(&:to_sym).each do |sig|
|
45
|
-
# This needs to be reentrant, so we queue up signals to be handled
|
46
|
-
# in the run loop, rather than acting on signals here
|
47
|
-
trap(sig) do
|
48
|
-
Thread.main[:signal_queue] << sig
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Handle any pending signals
|
54
|
-
def handle_signals
|
55
|
-
signal = Thread.main[:signal_queue].shift
|
56
|
-
if signal
|
57
|
-
logger.info "caught sig#{signal.downcase}, stopping hutch..."
|
58
|
-
stop
|
59
|
-
end
|
28
|
+
stop
|
60
29
|
end
|
61
30
|
|
62
31
|
# Stop a running worker by killing all subscriber threads.
|
@@ -64,36 +33,24 @@ module Hutch
|
|
64
33
|
@broker.stop
|
65
34
|
end
|
66
35
|
|
67
|
-
# Binds shutdown handler, called if channel is closed or network Failed
|
68
|
-
def bind_shutdown_handler
|
69
|
-
@broker.channel.on_shutdown do
|
70
|
-
Thread.main[:shutdown_received] = true
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Checks if shutdown handler was called, then sleeps for interval
|
75
|
-
def shutdown_not_called?(interval)
|
76
|
-
if Thread.main[:shutdown_received]
|
77
|
-
true
|
78
|
-
else
|
79
|
-
sleep(interval)
|
80
|
-
false
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
36
|
# Set up the queues for each of the worker's consumers.
|
85
37
|
def setup_queues
|
86
38
|
logger.info 'setting up queues'
|
87
|
-
@consumers.
|
39
|
+
vetted = @consumers.reject { |c| group_configured? && group_restricted?(c) }
|
40
|
+
vetted.each do |c|
|
41
|
+
setup_queue(c)
|
42
|
+
end
|
88
43
|
end
|
89
44
|
|
90
45
|
# Bind a consumer's routing keys to its queue, and set up a subscription to
|
91
46
|
# receive messages sent to the queue.
|
92
47
|
def setup_queue(consumer)
|
48
|
+
logger.info "setting up queue: #{consumer.get_queue_name}"
|
49
|
+
|
93
50
|
queue = @broker.queue(consumer.get_queue_name, consumer.get_arguments)
|
94
51
|
@broker.bind_queue(queue, consumer.routing_keys)
|
95
52
|
|
96
|
-
queue.subscribe(manual_ack: true) do |*args|
|
53
|
+
queue.subscribe(consumer_tag: unique_consumer_tag, manual_ack: true) do |*args|
|
97
54
|
delivery_info, properties, payload = Hutch::Adapter.decode_message(*args)
|
98
55
|
handle_message(consumer, delivery_info, properties, payload)
|
99
56
|
end
|
@@ -102,30 +59,39 @@ module Hutch
|
|
102
59
|
# Called internally when a new messages comes in from RabbitMQ. Responsible
|
103
60
|
# for wrapping up the message and passing it to the consumer.
|
104
61
|
def handle_message(consumer, delivery_info, properties, payload)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
62
|
+
serializer = consumer.get_serializer || Hutch::Config[:serializer]
|
63
|
+
logger.debug {
|
64
|
+
spec = serializer.binary? ? "#{payload.bytesize} bytes" : "#{payload}"
|
65
|
+
"message(#{properties.message_id || '-'}): " +
|
66
|
+
"routing key: #{delivery_info.routing_key}, " +
|
67
|
+
"consumer: #{consumer}, " +
|
68
|
+
"payload: #{spec}"
|
69
|
+
}
|
70
|
+
|
71
|
+
message = Message.new(delivery_info, properties, payload, serializer)
|
72
|
+
consumer_instance = consumer.new.tap { |c| c.broker, c.delivery_info = @broker, delivery_info }
|
73
|
+
with_tracing(consumer_instance).handle(message)
|
74
|
+
@broker.ack(delivery_info.delivery_tag)
|
75
|
+
rescue => ex
|
76
|
+
acknowledge_error(delivery_info, properties, @broker, ex)
|
77
|
+
handle_error(properties, payload, consumer, ex)
|
120
78
|
end
|
121
79
|
|
122
80
|
def with_tracing(klass)
|
123
81
|
Hutch::Config[:tracer].new(klass)
|
124
82
|
end
|
125
83
|
|
126
|
-
def handle_error(
|
84
|
+
def handle_error(*args)
|
127
85
|
Hutch::Config[:error_handlers].each do |backend|
|
128
|
-
backend.handle(
|
86
|
+
backend.handle(*args)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def acknowledge_error(delivery_info, properties, broker, ex)
|
91
|
+
acks = error_acknowledgements +
|
92
|
+
[Hutch::Acknowledgements::NackOnAllFailures.new]
|
93
|
+
acks.find do |backend|
|
94
|
+
backend.handle(delivery_info, properties, broker, ex)
|
129
95
|
end
|
130
96
|
end
|
131
97
|
|
@@ -135,5 +101,45 @@ module Hutch
|
|
135
101
|
end
|
136
102
|
@consumers = val
|
137
103
|
end
|
104
|
+
|
105
|
+
def error_acknowledgements
|
106
|
+
Hutch::Config[:error_acknowledgements]
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def group_configured?
|
112
|
+
if group.present? && consumer_groups.blank?
|
113
|
+
logger.info 'Consumer groups are blank'
|
114
|
+
end
|
115
|
+
group.present?
|
116
|
+
end
|
117
|
+
|
118
|
+
def group_restricted?(consumer)
|
119
|
+
consumers_to_load = consumer_groups[group]
|
120
|
+
if consumers_to_load
|
121
|
+
!consumers_to_load.include?(consumer.name)
|
122
|
+
else
|
123
|
+
true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def group
|
128
|
+
Hutch::Config[:group]
|
129
|
+
end
|
130
|
+
|
131
|
+
def consumer_groups
|
132
|
+
Hutch::Config[:consumer_groups]
|
133
|
+
end
|
134
|
+
|
135
|
+
attr_accessor :setup_procs
|
136
|
+
|
137
|
+
def unique_consumer_tag
|
138
|
+
prefix = Hutch::Config[:consumer_tag_prefix]
|
139
|
+
unique_part = SecureRandom.uuid
|
140
|
+
"#{prefix}-#{unique_part}".tap do |tag|
|
141
|
+
raise "Tag must be 255 bytes long at most, current one is #{tag.bytesize} ('#{tag}')" if tag.bytesize > 255
|
142
|
+
end
|
143
|
+
end
|
138
144
|
end
|
139
145
|
end
|
data/lib/hutch.rb
CHANGED
@@ -3,6 +3,8 @@ require 'hutch/consumer'
|
|
3
3
|
require 'hutch/worker'
|
4
4
|
require 'hutch/broker'
|
5
5
|
require 'hutch/logging'
|
6
|
+
require 'hutch/serializers/identity'
|
7
|
+
require 'hutch/serializers/json'
|
6
8
|
require 'hutch/config'
|
7
9
|
require 'hutch/message'
|
8
10
|
require 'hutch/cli'
|
@@ -12,6 +14,7 @@ require 'hutch/exceptions'
|
|
12
14
|
require 'hutch/tracers'
|
13
15
|
|
14
16
|
module Hutch
|
17
|
+
@@connection_mutex = Mutex.new
|
15
18
|
|
16
19
|
def self.register_consumer(consumer)
|
17
20
|
self.consumers << consumer
|
@@ -33,10 +36,17 @@ module Hutch
|
|
33
36
|
@global_properties ||= {}
|
34
37
|
end
|
35
38
|
|
39
|
+
# Connects to broker, if not yet connected.
|
40
|
+
#
|
41
|
+
# @param options [Hash] Connection options
|
42
|
+
# @param config [Hash] Configuration
|
43
|
+
# @option options [Boolean] :enable_http_api_use
|
36
44
|
def self.connect(options = {}, config = Hutch::Config)
|
37
|
-
|
38
|
-
|
39
|
-
|
45
|
+
@@connection_mutex.synchronize do
|
46
|
+
unless connected?
|
47
|
+
@broker = Hutch::Broker.new(config)
|
48
|
+
@broker.connect(options)
|
49
|
+
end
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
@@ -48,10 +58,9 @@ module Hutch
|
|
48
58
|
@broker
|
49
59
|
end
|
50
60
|
|
61
|
+
# @return [Boolean]
|
51
62
|
def self.connected?
|
52
|
-
|
53
|
-
return false unless broker.connection
|
54
|
-
broker.connection.open?
|
63
|
+
broker && broker.connection && broker.connection.open?
|
55
64
|
end
|
56
65
|
|
57
66
|
def self.publish(*args)
|