karafka 1.1.2 → 1.2.0.beta1
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/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/CHANGELOG.md +34 -0
- data/Gemfile +1 -2
- data/Gemfile.lock +35 -22
- data/README.md +4 -3
- data/karafka.gemspec +5 -3
- data/lib/karafka.rb +4 -5
- data/lib/karafka/app.rb +8 -15
- data/lib/karafka/attributes_map.rb +1 -1
- data/lib/karafka/backends/inline.rb +1 -2
- data/lib/karafka/{base_controller.rb → base_consumer.rb} +19 -11
- data/lib/karafka/base_responder.rb +33 -14
- data/lib/karafka/callbacks.rb +30 -0
- data/lib/karafka/callbacks/config.rb +22 -0
- data/lib/karafka/callbacks/dsl.rb +16 -0
- data/lib/karafka/cli/install.rb +2 -3
- data/lib/karafka/cli/server.rb +0 -1
- data/lib/karafka/connection/{consumer.rb → client.rb} +25 -33
- data/lib/karafka/connection/config_adapter.rb +14 -6
- data/lib/karafka/connection/delegator.rb +46 -0
- data/lib/karafka/connection/listener.rb +22 -13
- data/lib/karafka/{controllers → consumers}/callbacks.rb +9 -9
- data/lib/karafka/consumers/includer.rb +51 -0
- data/lib/karafka/consumers/responders.rb +24 -0
- data/lib/karafka/{controllers → consumers}/single_params.rb +3 -3
- data/lib/karafka/errors.rb +10 -3
- data/lib/karafka/fetcher.rb +30 -34
- data/lib/karafka/helpers/class_matcher.rb +8 -8
- data/lib/karafka/helpers/config_retriever.rb +2 -2
- data/lib/karafka/instrumentation/listener.rb +97 -0
- data/lib/karafka/instrumentation/logger.rb +55 -0
- data/lib/karafka/instrumentation/monitor.rb +62 -0
- data/lib/karafka/loader.rb +0 -1
- data/lib/karafka/params/{params.rb → dsl.rb} +69 -44
- data/lib/karafka/params/params_batch.rb +2 -2
- data/lib/karafka/patches/dry_configurable.rb +6 -2
- data/lib/karafka/patches/ruby_kafka.rb +10 -10
- data/lib/karafka/persistence/client.rb +25 -0
- data/lib/karafka/persistence/consumer.rb +27 -14
- data/lib/karafka/persistence/topic.rb +29 -0
- data/lib/karafka/process.rb +5 -4
- data/lib/karafka/responders/builder.rb +15 -14
- data/lib/karafka/routing/builder.rb +1 -1
- data/lib/karafka/routing/consumer_mapper.rb +3 -2
- data/lib/karafka/routing/router.rb +1 -1
- data/lib/karafka/routing/topic.rb +5 -5
- data/lib/karafka/schemas/config.rb +3 -0
- data/lib/karafka/schemas/consumer_group.rb +14 -2
- data/lib/karafka/schemas/consumer_group_topic.rb +1 -1
- data/lib/karafka/server.rb +33 -5
- data/lib/karafka/setup/config.rb +45 -21
- data/lib/karafka/setup/configurators/base.rb +6 -12
- data/lib/karafka/setup/configurators/params.rb +25 -0
- data/lib/karafka/setup/configurators/water_drop.rb +6 -3
- data/lib/karafka/setup/dsl.rb +22 -0
- data/lib/karafka/templates/{application_controller.rb.example → application_consumer.rb.example} +2 -3
- data/lib/karafka/templates/karafka.rb.example +14 -3
- data/lib/karafka/version.rb +1 -1
- metadata +58 -23
- data/lib/karafka/connection/processor.rb +0 -61
- data/lib/karafka/controllers/includer.rb +0 -51
- data/lib/karafka/controllers/responders.rb +0 -19
- data/lib/karafka/logger.rb +0 -53
- data/lib/karafka/monitor.rb +0 -98
- data/lib/karafka/persistence/controller.rb +0 -38
@@ -4,7 +4,7 @@ module Karafka
|
|
4
4
|
module Params
|
5
5
|
# Params batch represents a set of messages received from Kafka.
|
6
6
|
# @note Params internally are lazy loaded before first use. That way we can skip parsing
|
7
|
-
# process if we have
|
7
|
+
# process if we have after_fetch that rejects some incoming messages without using params
|
8
8
|
# It can be also used when handling really heavy data (in terms of parsing).
|
9
9
|
class ParamsBatch
|
10
10
|
include Enumerable
|
@@ -13,7 +13,7 @@ module Karafka
|
|
13
13
|
# @param messages_batch [Array<Kafka::FetchedMessage>] messages batch
|
14
14
|
# @param topic_parser [Class] topic parser for unparsing messages values
|
15
15
|
def initialize(messages_batch, topic_parser)
|
16
|
-
@params_batch = messages_batch.map do |message|
|
16
|
+
@params_batch = messages_batch.map! do |message|
|
17
17
|
Karafka::Params::Params.build(message, topic_parser)
|
18
18
|
end
|
19
19
|
end
|
@@ -19,11 +19,15 @@ module Karafka
|
|
19
19
|
private
|
20
20
|
|
21
21
|
# Method that rebuilds a given accessor, so when it consists a proc value, it will
|
22
|
-
# evaluate it upon return
|
22
|
+
# evaluate it upon return for blocks that don't require any arguments, otherwise
|
23
|
+
# it will return the block
|
23
24
|
# @param method_name [Symbol] name of an accessor that we want to rebuild
|
24
25
|
def rebuild(method_name)
|
25
26
|
define_singleton_method method_name do
|
26
|
-
|
27
|
+
value = super()
|
28
|
+
return value unless value.is_a?(Proc)
|
29
|
+
return value unless value.parameters.empty?
|
30
|
+
value.call
|
27
31
|
end
|
28
32
|
end
|
29
33
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Karafka
|
4
4
|
module Patches
|
5
|
-
#
|
5
|
+
# Patches for Ruby Kafka gem
|
6
6
|
module RubyKafka
|
7
7
|
# This patch allows us to inject business logic in between fetches and before the consumer
|
8
8
|
# stop, so we can perform stop commit or anything else that we need since
|
@@ -13,19 +13,19 @@ module Karafka
|
|
13
13
|
# thread)
|
14
14
|
def consumer_loop
|
15
15
|
super do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
consumers = Karafka::Persistence::Consumer
|
17
|
+
.all
|
18
|
+
.values
|
19
|
+
.flat_map(&:values)
|
20
|
+
.select { |ctrl| ctrl.respond_to?(:run_callbacks) }
|
21
21
|
|
22
22
|
if Karafka::App.stopped?
|
23
|
-
|
24
|
-
Karafka::Persistence::
|
23
|
+
consumers.each { |ctrl| ctrl.run_callbacks :before_stop }
|
24
|
+
Karafka::Persistence::Client.read.stop
|
25
25
|
else
|
26
|
-
|
26
|
+
consumers.each { |ctrl| ctrl.run_callbacks :before_poll }
|
27
27
|
yield
|
28
|
-
|
28
|
+
consumers.each { |ctrl| ctrl.run_callbacks :after_poll }
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Persistence
|
5
|
+
# Persistence layer to store current thread messages consumer client for further use
|
6
|
+
class Client
|
7
|
+
# Thread.current key under which we store current thread messages consumer client
|
8
|
+
PERSISTENCE_SCOPE = :client
|
9
|
+
|
10
|
+
# @param client [Karafka::Connection::Client] messages consumer client of
|
11
|
+
# a current thread
|
12
|
+
# @return [Karafka::Connection::Client] persisted messages consumer client
|
13
|
+
def self.write(client)
|
14
|
+
Thread.current[PERSISTENCE_SCOPE] = client
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Karafka::Connection::Client] persisted messages consumer client
|
18
|
+
# @raise [Karafka::Errors::MissingConsumer] raised when no thread messages consumer
|
19
|
+
# client but we try to use it anyway
|
20
|
+
def self.read
|
21
|
+
Thread.current[PERSISTENCE_SCOPE] || raise(Errors::MissingClient)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,24 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Karafka
|
4
|
+
# Module used to provide a persistent cache layer for Karafka components that need to be
|
5
|
+
# shared inside of a same thread
|
4
6
|
module Persistence
|
5
|
-
#
|
7
|
+
# Module used to provide a persistent cache across batch requests for a given
|
8
|
+
# topic and partition to store some additional details when the persistent mode
|
9
|
+
# for a given topic is turned on
|
6
10
|
class Consumer
|
7
|
-
# Thread.current
|
8
|
-
PERSISTENCE_SCOPE = :
|
11
|
+
# Thread.current scope under which we store consumers data
|
12
|
+
PERSISTENCE_SCOPE = :consumers
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
class << self
|
15
|
+
# @return [Hash] current thread persistence scope hash with all the consumers
|
16
|
+
def all
|
17
|
+
# @note This does not need to be threadsafe (Hash) as it is always executed in a
|
18
|
+
# current thread context
|
19
|
+
Thread.current[PERSISTENCE_SCOPE] ||= Hash.new { |hash, key| hash[key] = {} }
|
20
|
+
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
# Used to build (if block given) and/or fetch a current consumer instance that will be
|
23
|
+
# used to process messages from a given topic and partition
|
24
|
+
# @return [Karafka::BaseConsumer] base consumer descendant
|
25
|
+
# @param topic [Karafka::Routing::Topic] topic instance for which we might cache
|
26
|
+
# @param partition [Integer] number of partition for which we want to cache
|
27
|
+
def fetch(topic, partition)
|
28
|
+
# We always store a current instance for callback reasons
|
29
|
+
if topic.persistent
|
30
|
+
all[topic][partition] ||= topic.consumer.new
|
31
|
+
else
|
32
|
+
all[topic][partition] = topic.consumer.new
|
33
|
+
end
|
34
|
+
end
|
22
35
|
end
|
23
36
|
end
|
24
37
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Persistence
|
5
|
+
# Local cache for routing topics
|
6
|
+
# We use it in order not to build string instances and remap incoming topic upon each
|
7
|
+
# message / message batches received
|
8
|
+
class Topic
|
9
|
+
# Thread.current scope under which we store topics data
|
10
|
+
PERSISTENCE_SCOPE = :topics
|
11
|
+
|
12
|
+
# @param group_id [String] group id for which we fetch a topic representation
|
13
|
+
# @param raw_topic_name [String] raw topic name (before remapping) for which we fetch a
|
14
|
+
# topic representation
|
15
|
+
# @return [Karafka::Routing::Topic] remapped topic representation that can be used further
|
16
|
+
# on when working with given parameters
|
17
|
+
def self.fetch(group_id, raw_topic_name)
|
18
|
+
Thread.current[PERSISTENCE_SCOPE] ||= Hash.new { |hash, key| hash[key] = {} }
|
19
|
+
|
20
|
+
Thread.current[PERSISTENCE_SCOPE][group_id][raw_topic_name] ||= begin
|
21
|
+
# We map from incoming topic name, as it might be namespaced, etc.
|
22
|
+
# @see topic_mapper internal docs
|
23
|
+
mapped_topic_name = Karafka::App.config.topic_mapper.incoming(raw_topic_name)
|
24
|
+
Routing::Router.find("#{group_id}_#{mapped_topic_name}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/karafka/process.rb
CHANGED
@@ -8,7 +8,9 @@ module Karafka
|
|
8
8
|
|
9
9
|
# Signal types that we handle
|
10
10
|
HANDLED_SIGNALS = %i[
|
11
|
-
SIGINT
|
11
|
+
SIGINT
|
12
|
+
SIGQUIT
|
13
|
+
SIGTERM
|
12
14
|
].freeze
|
13
15
|
|
14
16
|
HANDLED_SIGNALS.each do |signal|
|
@@ -27,8 +29,7 @@ module Karafka
|
|
27
29
|
|
28
30
|
# Creates an instance of process and creates empty hash for callbacks
|
29
31
|
def initialize
|
30
|
-
@callbacks = {}
|
31
|
-
HANDLED_SIGNALS.each { |signal| @callbacks[signal] = [] }
|
32
|
+
@callbacks = Hash.new { |hsh, key| hsh[key] = [] }
|
32
33
|
end
|
33
34
|
|
34
35
|
# Method catches all HANDLED_SIGNALS and performs appropriate callbacks (if defined)
|
@@ -56,7 +57,7 @@ module Karafka
|
|
56
57
|
# we have to spin up a new thread to do this
|
57
58
|
def notice_signal(signal)
|
58
59
|
Thread.new do
|
59
|
-
Karafka.monitor.
|
60
|
+
Karafka.monitor.instrument('process.notice_signal', caller: self, signal: signal)
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
@@ -3,30 +3,31 @@
|
|
3
3
|
module Karafka
|
4
4
|
# Responders namespace encapsulates all the internal responder implementation parts
|
5
5
|
module Responders
|
6
|
-
# Responders builder is used to
|
7
|
-
# match the
|
8
|
-
# but he still names responder with the same convention (and namespaces) as
|
6
|
+
# Responders builder is used to finding (based on the consumer class name) a responder
|
7
|
+
# that match the consumer. We use it when user does not provide a responder inside routing,
|
8
|
+
# but he still names responder with the same convention (and namespaces) as consumer
|
9
|
+
#
|
9
10
|
# @example Matching responder exists
|
10
|
-
# Karafka::Responder::Builder(
|
11
|
+
# Karafka::Responder::Builder(NewEventsConsumer).build #=> NewEventsResponder
|
11
12
|
# @example Matching responder does not exist
|
12
|
-
# Karafka::Responder::Builder(
|
13
|
+
# Karafka::Responder::Builder(NewBuildsConsumer).build #=> nil
|
13
14
|
class Builder
|
14
|
-
# @param
|
15
|
-
# Karafka::
|
16
|
-
# @example Tries to find a responder that matches a given
|
17
|
-
# will return nil (nil is accepted, because it means that a given
|
15
|
+
# @param consumer_class [Karafka::BaseConsumer, nil] descendant of
|
16
|
+
# Karafka::BaseConsumer
|
17
|
+
# @example Tries to find a responder that matches a given consumer. If nothing found,
|
18
|
+
# will return nil (nil is accepted, because it means that a given consumer don't
|
18
19
|
# pipe stuff further on)
|
19
|
-
def initialize(
|
20
|
-
@
|
20
|
+
def initialize(consumer_class)
|
21
|
+
@consumer_class = consumer_class
|
21
22
|
end
|
22
23
|
|
23
|
-
# Tries to figure out a responder based on a
|
24
|
+
# Tries to figure out a responder based on a consumer class name
|
24
25
|
# @return [Class] Responder class (not an instance)
|
25
26
|
# @return [nil] or nil if there's no matching responding class
|
26
27
|
def build
|
27
28
|
Helpers::ClassMatcher.new(
|
28
|
-
@
|
29
|
-
from: '
|
29
|
+
@consumer_class,
|
30
|
+
from: 'Consumer',
|
30
31
|
to: 'Responder'
|
31
32
|
).match
|
32
33
|
end
|
@@ -17,7 +17,7 @@ module Karafka
|
|
17
17
|
# module MyMapper
|
18
18
|
# def self.call(raw_consumer_group_name)
|
19
19
|
# [
|
20
|
-
# Karafka::App.config.client_id.to_s
|
20
|
+
# Dry::Inflector.new.underscore(Karafka::App.config.client_id.to_s),
|
21
21
|
# raw_consumer_group_name
|
22
22
|
# ].join('_').gsub('_', '.')
|
23
23
|
# end
|
@@ -26,7 +26,8 @@ module Karafka
|
|
26
26
|
# @param raw_consumer_group_name [String, Symbol] string or symbolized consumer group name
|
27
27
|
# @return [String] remapped final consumer group name
|
28
28
|
def self.call(raw_consumer_group_name)
|
29
|
-
|
29
|
+
client_name = Dry::Inflector.new.underscore(Karafka::App.config.client_id.to_s)
|
30
|
+
"#{client_name}_#{raw_consumer_group_name}"
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Karafka
|
4
4
|
# Namespace for all elements related to requests routing
|
5
5
|
module Routing
|
6
|
-
# Karafka framework Router for routing incoming messages to proper
|
6
|
+
# Karafka framework Router for routing incoming messages to proper consumers
|
7
7
|
# @note Since Kafka does not provide namespaces or modules for topics, they all have "flat"
|
8
8
|
# structure so all the routes are being stored in a single level array
|
9
9
|
module Router
|
@@ -9,7 +9,7 @@ module Karafka
|
|
9
9
|
extend Helpers::ConfigRetriever
|
10
10
|
|
11
11
|
attr_reader :id, :consumer_group
|
12
|
-
attr_accessor :
|
12
|
+
attr_accessor :consumer
|
13
13
|
|
14
14
|
# @param [String, Symbol] name of a topic on which we want to listen
|
15
15
|
# @param consumer_group [Karafka::Routing::ConsumerGroup] owning consumer group of this topic
|
@@ -29,14 +29,14 @@ module Karafka
|
|
29
29
|
# example for Sidekiq
|
30
30
|
def build
|
31
31
|
Karafka::AttributesMap.topic.each { |attr| send(attr) }
|
32
|
-
|
32
|
+
consumer&.topic = self
|
33
33
|
self
|
34
34
|
end
|
35
35
|
|
36
36
|
# @return [Class, nil] Class (not an instance) of a responder that should respond from
|
37
|
-
#
|
37
|
+
# consumer back to Kafka (usefull for piping dataflows)
|
38
38
|
def responder
|
39
|
-
@responder ||= Karafka::Responders::Builder.new(
|
39
|
+
@responder ||= Karafka::Responders::Builder.new(consumer).build
|
40
40
|
end
|
41
41
|
|
42
42
|
Karafka::AttributesMap.topic.each do |attribute|
|
@@ -52,7 +52,7 @@ module Karafka
|
|
52
52
|
|
53
53
|
Hash[map].merge!(
|
54
54
|
id: id,
|
55
|
-
|
55
|
+
consumer: consumer
|
56
56
|
)
|
57
57
|
end
|
58
58
|
end
|
@@ -13,8 +13,11 @@ module Karafka
|
|
13
13
|
# so we validate all of that once all the routes are defined and ready
|
14
14
|
Config = Dry::Validation.Schema do
|
15
15
|
required(:client_id).filled(:str?, format?: Karafka::Schemas::TOPIC_REGEXP)
|
16
|
+
required(:shutdown_timeout) { none? | (int? & gteq?(0)) }
|
16
17
|
required(:consumer_mapper)
|
17
18
|
required(:topic_mapper)
|
19
|
+
required(:params_base_class).filled
|
20
|
+
|
18
21
|
optional(:backend).filled
|
19
22
|
end
|
20
23
|
end
|
@@ -9,6 +9,9 @@ module Karafka
|
|
9
9
|
# but someetimes loads things twice
|
10
10
|
URI_SCHEMES ||= %w[kafka kafka+ssl].freeze
|
11
11
|
|
12
|
+
# Available sasl scram mechanism of authentication (plus nil)
|
13
|
+
SASL_SCRAM_MECHANISMS ||= %w[sha256 sha512].freeze
|
14
|
+
|
12
15
|
configure do
|
13
16
|
config.messages_file = File.join(
|
14
17
|
Karafka.gem_root, 'config', 'errors.yml'
|
@@ -36,6 +39,7 @@ module Karafka
|
|
36
39
|
required(:connect_timeout).filled { (int? | float?) & gt?(0) }
|
37
40
|
required(:socket_timeout).filled { (int? | float?) & gt?(0) }
|
38
41
|
required(:min_bytes).filled(:int?, gt?: 0)
|
42
|
+
required(:max_bytes).filled(:int?, gt?: 0)
|
39
43
|
required(:max_wait_time).filled { (int? | float?) & gteq?(0) }
|
40
44
|
required(:batch_fetching).filled(:bool?)
|
41
45
|
required(:topics).filled { each { schema(ConsumerGroupTopic) } }
|
@@ -52,14 +56,22 @@ module Karafka
|
|
52
56
|
ssl_ca_cert_file_path
|
53
57
|
ssl_client_cert
|
54
58
|
ssl_client_cert_key
|
59
|
+
sasl_gssapi_principal
|
60
|
+
sasl_gssapi_keytab
|
55
61
|
sasl_plain_authzid
|
56
62
|
sasl_plain_username
|
57
63
|
sasl_plain_password
|
58
|
-
|
59
|
-
|
64
|
+
sasl_scram_username
|
65
|
+
sasl_scram_password
|
60
66
|
].each do |encryption_attribute|
|
61
67
|
optional(encryption_attribute).maybe(:str?)
|
62
68
|
end
|
69
|
+
|
70
|
+
optional(:ssl_ca_certs_from_system).maybe(:bool?)
|
71
|
+
|
72
|
+
# It's not with other encryptions as it has some more rules
|
73
|
+
optional(:sasl_scram_mechanism)
|
74
|
+
.maybe(:str?, included_in?: Karafka::Schemas::SASL_SCRAM_MECHANISMS)
|
63
75
|
end
|
64
76
|
end
|
65
77
|
end
|
@@ -7,7 +7,7 @@ module Karafka
|
|
7
7
|
required(:id).filled(:str?, format?: Karafka::Schemas::TOPIC_REGEXP)
|
8
8
|
required(:name).filled(:str?, format?: Karafka::Schemas::TOPIC_REGEXP)
|
9
9
|
required(:backend).filled(included_in?: %i[inline sidekiq])
|
10
|
-
required(:
|
10
|
+
required(:consumer).filled
|
11
11
|
required(:parser).filled
|
12
12
|
required(:max_bytes_per_partition).filled(:int?, gteq?: 0)
|
13
13
|
required(:start_from_beginning).filled(:bool?)
|
data/lib/karafka/server.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
module Karafka
|
4
4
|
# Karafka consuming server class
|
5
5
|
class Server
|
6
|
+
@consumer_threads = Concurrent::Array.new
|
7
|
+
|
8
|
+
# How long should we sleep between checks on shutting down consumers
|
9
|
+
SUPERVISION_SLEEP = 1
|
10
|
+
# What system exit code should we use when we terminated forcefully
|
11
|
+
FORCEFUL_EXIT_CODE = 2
|
12
|
+
|
6
13
|
class << self
|
7
14
|
# Set of consuming threads. Each consumer thread contains a single consumer
|
8
15
|
attr_accessor :consumer_threads
|
@@ -12,7 +19,6 @@ module Karafka
|
|
12
19
|
|
13
20
|
# Method which runs app
|
14
21
|
def run
|
15
|
-
@consumer_threads = Concurrent::Array.new
|
16
22
|
bind_on_sigint
|
17
23
|
bind_on_sigquit
|
18
24
|
bind_on_sigterm
|
@@ -35,17 +41,17 @@ module Karafka
|
|
35
41
|
|
36
42
|
# What should happen when we decide to quit with sigint
|
37
43
|
def bind_on_sigint
|
38
|
-
process.on_sigint {
|
44
|
+
process.on_sigint { stop_supervised }
|
39
45
|
end
|
40
46
|
|
41
47
|
# What should happen when we decide to quit with sigquit
|
42
48
|
def bind_on_sigquit
|
43
|
-
process.on_sigquit {
|
49
|
+
process.on_sigquit { stop_supervised }
|
44
50
|
end
|
45
51
|
|
46
52
|
# What should happen when we decide to quit with sigterm
|
47
53
|
def bind_on_sigterm
|
48
|
-
process.on_sigterm {
|
54
|
+
process.on_sigterm { stop_supervised }
|
49
55
|
end
|
50
56
|
|
51
57
|
# Starts Karafka with a supervision
|
@@ -54,8 +60,30 @@ module Karafka
|
|
54
60
|
def start_supervised
|
55
61
|
process.supervise do
|
56
62
|
Karafka::App.run!
|
57
|
-
Karafka::Fetcher.
|
63
|
+
Karafka::Fetcher.call
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Stops Karafka with a supervision (as long as there is a shutdown timeout)
|
68
|
+
# If consumers won't stop in a given timeframe, it will force them to exit
|
69
|
+
def stop_supervised
|
70
|
+
Karafka::App.stop!
|
71
|
+
|
72
|
+
# If there is no shutdown timeout, we don't exit and wait until all the consumers
|
73
|
+
# had done their work
|
74
|
+
return unless Karafka::App.config.shutdown_timeout
|
75
|
+
|
76
|
+
# If there is a timeout, we check every 1 second (for the timeout period) if all
|
77
|
+
# the threads finished their work and if so, we can just return and normal
|
78
|
+
# shutdown process will take place
|
79
|
+
Karafka::App.config.shutdown_timeout.to_i.times do
|
80
|
+
return if consumer_threads.count(&:alive?).zero?
|
81
|
+
sleep SUPERVISION_SLEEP
|
58
82
|
end
|
83
|
+
|
84
|
+
# We're done waiting, lets kill them!
|
85
|
+
consumer_threads.each(&:terminate)
|
86
|
+
Kernel.exit FORCEFUL_EXIT_CODE
|
59
87
|
end
|
60
88
|
end
|
61
89
|
end
|