hutch 0.18.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|