karafka 2.0.0.beta2 → 2.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile.lock +4 -4
- data/bin/integrations +36 -14
- data/bin/scenario +29 -0
- data/docker-compose.yml +2 -0
- data/karafka.gemspec +1 -1
- data/lib/karafka/active_job/routing/extensions.rb +11 -1
- data/lib/karafka/base_consumer.rb +41 -26
- data/lib/karafka/connection/client.rb +38 -15
- data/lib/karafka/connection/listener.rb +17 -13
- data/lib/karafka/contracts/config.rb +2 -1
- data/lib/karafka/instrumentation/logger_listener.rb +37 -10
- data/lib/karafka/instrumentation/monitor.rb +3 -0
- data/lib/karafka/licenser.rb +26 -7
- data/lib/karafka/pro/active_job/consumer.rb +36 -9
- data/lib/karafka/pro/active_job/dispatcher.rb +9 -9
- data/lib/karafka/pro/active_job/job_options_contract.rb +9 -9
- data/lib/karafka/pro/base_consumer_extensions.rb +66 -0
- data/lib/karafka/pro/loader.rb +29 -15
- data/lib/karafka/pro/performance_tracker.rb +9 -9
- data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +9 -10
- data/lib/karafka/pro/processing/jobs_builder.rb +31 -0
- data/lib/karafka/pro/routing/extensions.rb +32 -0
- data/lib/karafka/pro/scheduler.rb +9 -9
- data/lib/karafka/processing/executor.rb +8 -1
- data/lib/karafka/processing/jobs_builder.rb +28 -0
- data/lib/karafka/processing/worker.rb +17 -9
- data/lib/karafka/routing/subscription_group.rb +1 -1
- data/lib/karafka/setup/config.rb +3 -1
- data/lib/karafka/status.rb +1 -3
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +8 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86b352cc7737bde8484567662d546165793496adb6a923e33aa7e4823e4a456f
|
4
|
+
data.tar.gz: 2a180ba7b177db9f4c67de387262d47ac3eb2580860a548992912cd89a049da7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 207700c3e1fab4d3370de7eddae72453fc180c65d817f4d7c021929327e5464fcd8a9bb1610c641080f82a60bc038e4517dc7bc86c0aa4c83852dd9467441e75
|
7
|
+
data.tar.gz: ab94239255ff841e0728c6e6585d4145a9c4f42dfd02d8df062975bd28ad3caf2cef50f5488d30a1356ad49e2529f772379bc947ba5804a15f12e56989cf9a7a
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# Karafka framework changelog
|
2
2
|
|
3
|
+
## 2.0.0-beta3 (2022-06-14)
|
4
|
+
- Jobs building responsibility extracted out of the listener code base.
|
5
|
+
- Fix a case where specs supervisor would try to kill no longer running process (#868)
|
6
|
+
- Fix an instable integration spec that could misbehave under load
|
7
|
+
- Commit offsets prior to pausing partitions to ensure that the latest offset is always committed
|
8
|
+
- Fix a case where consecutive CTRL+C (non-stop) would case an exception during forced shutdown
|
9
|
+
- Add missing `consumer.prepared.error` into `LoggerListener`
|
10
|
+
- Delegate partition resuming from the consumers to listeners threads.
|
11
|
+
- Add support for Long Running Jobs (LRJ) for ActiveJob [PRO]
|
12
|
+
- Add support for Long Running Jobs for consumers [PRO]
|
13
|
+
- Allow `active_job_topic` to accept a block for extra topic related settings
|
14
|
+
- Remove no longer needed logger threads
|
15
|
+
- Auto-adapt number of processes for integration specs based on the number of CPUs
|
16
|
+
- Introduce an integration spec runner that prints everything to stdout (better for development)
|
17
|
+
- Introduce extra integration specs for various ActiveJob usage scenarios
|
18
|
+
- Rename consumer method `#prepared` to `#prepare` to reflect better its use-case
|
19
|
+
- For test and dev raise an error when expired license key is used (never for non dev)
|
20
|
+
- Add worker related monitor events (`worker.process` and `worker.processed`)
|
21
|
+
- Update `LoggerListener` to include more useful information about processing and polling messages
|
22
|
+
|
3
23
|
## 2.0.0-beta2 (2022-06-07)
|
4
24
|
- Abstract away notion of topics groups (until now it was just an array)
|
5
25
|
- Optimize how jobs queue is closed. Since we enqueue jobs only from the listeners, we can safely close jobs queue once listeners are done. By extracting this responsibility from listeners, we remove corner cases and race conditions. Note here: for non-blocking jobs we do wait for them to finish while running the `poll`. This ensures, that for async jobs that are long-living, we do not reach `max.poll.interval`.
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
karafka (2.0.0.
|
4
|
+
karafka (2.0.0.beta3)
|
5
5
|
dry-configurable (~> 0.13)
|
6
6
|
dry-monitor (~> 0.5)
|
7
7
|
dry-validation (~> 1.7)
|
@@ -45,7 +45,7 @@ GEM
|
|
45
45
|
dry-configurable (~> 0.13, >= 0.13.0)
|
46
46
|
dry-core (~> 0.5, >= 0.5)
|
47
47
|
dry-events (~> 0.2)
|
48
|
-
dry-schema (1.9.
|
48
|
+
dry-schema (1.9.2)
|
49
49
|
concurrent-ruby (~> 1.0)
|
50
50
|
dry-configurable (~> 0.13, >= 0.13.0)
|
51
51
|
dry-core (~> 0.5, >= 0.5)
|
@@ -58,12 +58,12 @@ GEM
|
|
58
58
|
dry-core (~> 0.5, >= 0.5)
|
59
59
|
dry-inflector (~> 0.1, >= 0.1.2)
|
60
60
|
dry-logic (~> 1.0, >= 1.0.2)
|
61
|
-
dry-validation (1.8.
|
61
|
+
dry-validation (1.8.1)
|
62
62
|
concurrent-ruby (~> 1.0)
|
63
63
|
dry-container (~> 0.7, >= 0.7.1)
|
64
64
|
dry-core (~> 0.5, >= 0.5)
|
65
65
|
dry-initializer (~> 3.0)
|
66
|
-
dry-schema (~> 1.
|
66
|
+
dry-schema (~> 1.8, >= 1.8.0)
|
67
67
|
factory_bot (6.2.1)
|
68
68
|
activesupport (>= 5.0.0)
|
69
69
|
ffi (1.15.5)
|
data/bin/integrations
CHANGED
@@ -11,21 +11,21 @@ require 'open3'
|
|
11
11
|
require 'fileutils'
|
12
12
|
require 'pathname'
|
13
13
|
require 'tmpdir'
|
14
|
+
require 'etc'
|
14
15
|
|
15
16
|
ROOT_PATH = Pathname.new(File.expand_path(File.join(File.dirname(__FILE__), '../')))
|
16
17
|
|
17
|
-
# Raised from the parent process if any of the integration tests fails
|
18
|
-
IntegrationTestError = Class.new(StandardError)
|
19
|
-
|
20
18
|
# How many child processes with integration specs do we want to run in parallel
|
21
|
-
# When the value is high, there's a problem with thread allocation on Github
|
22
|
-
|
19
|
+
# When the value is high, there's a problem with thread allocation on Github CI, tht is why
|
20
|
+
# we limit it. Locally we can run a lot of those, as many of them have sleeps and do not use a lot
|
21
|
+
# of CPU
|
22
|
+
CONCURRENCY = ENV.key?('CI') ? 5 : Etc.nprocessors * 2
|
23
23
|
|
24
24
|
# Abstraction around a single test scenario execution process
|
25
25
|
class Scenario
|
26
26
|
# How long a scenario can run before we kill it
|
27
27
|
# This is a fail-safe just in case something would hang
|
28
|
-
MAX_RUN_TIME =
|
28
|
+
MAX_RUN_TIME = 3 * 60 # 3 minutes tops
|
29
29
|
|
30
30
|
# There are rare cases where Karafka may force shutdown for some of the integration cases
|
31
31
|
# This includes exactly those
|
@@ -73,7 +73,12 @@ class Scenario
|
|
73
73
|
# If the thread is running too long, kill it
|
74
74
|
if current_time - @started_at > MAX_RUN_TIME
|
75
75
|
@wait_thr.kill
|
76
|
-
|
76
|
+
|
77
|
+
begin
|
78
|
+
Process.kill('TERM', pid)
|
79
|
+
# It may finish right after we want to kill it, that's why we ignore this
|
80
|
+
rescue Errno::ESRCH
|
81
|
+
end
|
77
82
|
end
|
78
83
|
|
79
84
|
# We read it so it won't grow as we use our default logger that prints to both test.log and
|
@@ -106,14 +111,15 @@ class Scenario
|
|
106
111
|
|
107
112
|
# Prints a status report when scenario is finished and stdout if it failed
|
108
113
|
def report
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
+
if success?
|
115
|
+
print "\e[#{32}m#{'.'}\e[0m"
|
116
|
+
else
|
117
|
+
puts
|
118
|
+
puts "\e[#{31}m#{'[FAILED]'}\e[0m #{name}"
|
114
119
|
puts "Exit code: #{exit_code}"
|
115
120
|
puts @stdout_tail
|
116
121
|
puts @stderr.read
|
122
|
+
puts
|
117
123
|
end
|
118
124
|
end
|
119
125
|
|
@@ -204,5 +210,21 @@ while finished_scenarios.size < scenarios.size
|
|
204
210
|
sleep(0.1)
|
205
211
|
end
|
206
212
|
|
207
|
-
|
208
|
-
|
213
|
+
failed_scenarios = finished_scenarios.reject(&:success?)
|
214
|
+
|
215
|
+
# Report once more on the failed jobs
|
216
|
+
# This will only list scenarios that failed without printing their stdout here.
|
217
|
+
if failed_scenarios.empty?
|
218
|
+
puts
|
219
|
+
else
|
220
|
+
puts "\nFailed scenarios:\n\n"
|
221
|
+
|
222
|
+
failed_scenarios.each do |scenario|
|
223
|
+
puts "\e[#{31}m#{'[FAILED]'}\e[0m #{scenario.name}"
|
224
|
+
end
|
225
|
+
|
226
|
+
puts
|
227
|
+
|
228
|
+
# Exit with 1 if not all scenarios were successful
|
229
|
+
exit 1
|
230
|
+
end
|
data/bin/scenario
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Runner for non-parallel execution of a single scenario.
|
4
|
+
# It prints all the info stdout, etc and basically replaces itself with the scenario execution.
|
5
|
+
# It is useful when we work with a single spec and we need all the debug info
|
6
|
+
|
7
|
+
raise 'This code needs to be executed WITHOUT bundle exec' if Kernel.const_defined?(:Bundler)
|
8
|
+
|
9
|
+
require 'open3'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'pathname'
|
12
|
+
require 'tmpdir'
|
13
|
+
require 'etc'
|
14
|
+
|
15
|
+
ROOT_PATH = Pathname.new(File.expand_path(File.join(File.dirname(__FILE__), '../')))
|
16
|
+
|
17
|
+
# Load all the specs
|
18
|
+
specs = Dir[ROOT_PATH.join('spec/integrations/**/*.rb')]
|
19
|
+
|
20
|
+
# If filters is provided, apply
|
21
|
+
# Allows to provide several filters one after another and applies all of them
|
22
|
+
ARGV.each do |filter|
|
23
|
+
specs.delete_if { |name| !name.include?(filter) }
|
24
|
+
end
|
25
|
+
|
26
|
+
raise ArgumentError, "No integration specs with filters: #{ARGV.join(', ')}" if specs.empty?
|
27
|
+
raise ArgumentError, "Many specs found with filters: #{ARGV.join(', ')}" if specs.size != 1
|
28
|
+
|
29
|
+
exec("bundle exec ruby -r #{ROOT_PATH}/spec/integrations_helper.rb #{specs[0]}")
|
data/docker-compose.yml
CHANGED
data/karafka.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = 'https://karafka.io'
|
15
15
|
spec.summary = 'Ruby based framework for working with Apache Kafka'
|
16
16
|
spec.description = 'Framework used to simplify Apache Kafka based Ruby applications development'
|
17
|
-
spec.
|
17
|
+
spec.licenses = ['LGPL-3.0', 'Commercial']
|
18
18
|
|
19
19
|
spec.add_dependency 'dry-configurable', '~> 0.13'
|
20
20
|
spec.add_dependency 'dry-monitor', '~> 0.5'
|
@@ -10,9 +10,19 @@ module Karafka
|
|
10
10
|
# This method simplifies routes definition for ActiveJob topics / queues by auto-injecting
|
11
11
|
# the consumer class
|
12
12
|
# @param name [String, Symbol] name of the topic where ActiveJobs jobs should go
|
13
|
-
|
13
|
+
# @param block [Proc] block that we can use for some extra configuration
|
14
|
+
def active_job_topic(name, &block)
|
14
15
|
topic(name) do
|
15
16
|
consumer App.config.internal.active_job.consumer
|
17
|
+
|
18
|
+
next unless block
|
19
|
+
|
20
|
+
instance_eval(&block)
|
21
|
+
|
22
|
+
# This is handled by our custom ActiveJob consumer
|
23
|
+
# Without this, default behaviour would cause messages to skip upon shutdown as the
|
24
|
+
# offset would be committed for the last message
|
25
|
+
manual_offset_management true
|
16
26
|
end
|
17
27
|
end
|
18
28
|
end
|
@@ -15,8 +15,33 @@ module Karafka
|
|
15
15
|
# @return [Waterdrop::Producer] producer instance
|
16
16
|
attr_accessor :producer
|
17
17
|
|
18
|
+
# Can be used to run preparation code
|
19
|
+
#
|
20
|
+
# @private
|
21
|
+
# @note This should not be used by the end users as it is part of the lifecycle of things but
|
22
|
+
# not as part of the public api. This can act as a hook when creating non-blocking
|
23
|
+
# consumers and doing other advanced stuff
|
24
|
+
def on_prepare
|
25
|
+
Karafka.monitor.instrument('consumer.prepared', caller: self) do
|
26
|
+
prepare
|
27
|
+
end
|
28
|
+
|
29
|
+
true
|
30
|
+
rescue StandardError => e
|
31
|
+
Karafka.monitor.instrument(
|
32
|
+
'error.occurred',
|
33
|
+
error: e,
|
34
|
+
caller: self,
|
35
|
+
type: 'consumer.prepare.error'
|
36
|
+
)
|
37
|
+
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
18
41
|
# Executes the default consumer flow.
|
19
42
|
#
|
43
|
+
# @return [Boolean] true if there was no exception, otherwise false.
|
44
|
+
#
|
20
45
|
# @note We keep the seek offset tracking, and use it to compensate for async offset flushing
|
21
46
|
# that may not yet kick in when error occurs. That way we pause always on the last processed
|
22
47
|
# message.
|
@@ -33,6 +58,8 @@ module Karafka
|
|
33
58
|
# with manual offset management
|
34
59
|
mark_as_consumed(messages.last)
|
35
60
|
end
|
61
|
+
|
62
|
+
true
|
36
63
|
rescue StandardError => e
|
37
64
|
Karafka.monitor.instrument(
|
38
65
|
'error.occurred',
|
@@ -42,6 +69,8 @@ module Karafka
|
|
42
69
|
)
|
43
70
|
|
44
71
|
pause(@seek_offset || messages.first.offset)
|
72
|
+
|
73
|
+
false
|
45
74
|
end
|
46
75
|
|
47
76
|
# Trigger method for running on shutdown.
|
@@ -51,6 +80,8 @@ module Karafka
|
|
51
80
|
Karafka.monitor.instrument('consumer.revoked', caller: self) do
|
52
81
|
revoked
|
53
82
|
end
|
83
|
+
|
84
|
+
true
|
54
85
|
rescue StandardError => e
|
55
86
|
Karafka.monitor.instrument(
|
56
87
|
'error.occurred',
|
@@ -58,6 +89,8 @@ module Karafka
|
|
58
89
|
caller: self,
|
59
90
|
type: 'consumer.revoked.error'
|
60
91
|
)
|
92
|
+
|
93
|
+
false
|
61
94
|
end
|
62
95
|
|
63
96
|
# Trigger method for running on shutdown.
|
@@ -67,6 +100,8 @@ module Karafka
|
|
67
100
|
Karafka.monitor.instrument('consumer.shutdown', caller: self) do
|
68
101
|
shutdown
|
69
102
|
end
|
103
|
+
|
104
|
+
true
|
70
105
|
rescue StandardError => e
|
71
106
|
Karafka.monitor.instrument(
|
72
107
|
'error.occurred',
|
@@ -74,32 +109,15 @@ module Karafka
|
|
74
109
|
caller: self,
|
75
110
|
type: 'consumer.shutdown.error'
|
76
111
|
)
|
77
|
-
end
|
78
112
|
|
79
|
-
|
80
|
-
#
|
81
|
-
# @private
|
82
|
-
# @note This should not be used by the end users as it is part of the lifecycle of things but
|
83
|
-
# not as part of the public api. This can act as a hook when creating non-blocking
|
84
|
-
# consumers and doing other advanced stuff
|
85
|
-
def on_prepared
|
86
|
-
Karafka.monitor.instrument('consumer.prepared', caller: self) do
|
87
|
-
prepared
|
88
|
-
end
|
89
|
-
rescue StandardError => e
|
90
|
-
Karafka.monitor.instrument(
|
91
|
-
'error.occurred',
|
92
|
-
error: e,
|
93
|
-
caller: self,
|
94
|
-
type: 'consumer.prepared.error'
|
95
|
-
)
|
113
|
+
false
|
96
114
|
end
|
97
115
|
|
98
116
|
private
|
99
117
|
|
100
118
|
# Method that gets called in the blocking flow allowing to setup any type of resources or to
|
101
119
|
# send additional commands to Kafka before the proper execution starts.
|
102
|
-
def
|
120
|
+
def prepare; end
|
103
121
|
|
104
122
|
# Method that will perform business logic and on data received from Kafka (it will consume
|
105
123
|
# the data)
|
@@ -144,22 +162,19 @@ module Karafka
|
|
144
162
|
# @param timeout [Integer, nil] how long in milliseconds do we want to pause or nil to use the
|
145
163
|
# default exponential pausing strategy defined for retries
|
146
164
|
def pause(offset, timeout = nil)
|
165
|
+
timeout ? pause_tracker.pause(timeout) : pause_tracker.pause
|
166
|
+
|
147
167
|
client.pause(
|
148
168
|
messages.metadata.topic,
|
149
169
|
messages.metadata.partition,
|
150
170
|
offset
|
151
171
|
)
|
152
|
-
|
153
|
-
timeout ? pause_tracker.pause(timeout) : pause_tracker.pause
|
154
172
|
end
|
155
173
|
|
156
174
|
# Resumes processing of the current topic partition
|
157
175
|
def resume
|
158
|
-
|
159
|
-
|
160
|
-
messages.metadata.partition
|
161
|
-
)
|
162
|
-
|
176
|
+
# This is sufficient to expire a partition pause, as with it will be resumed by the listener
|
177
|
+
# thread before the next poll.
|
163
178
|
pause_tracker.expire
|
164
179
|
end
|
165
180
|
|
@@ -86,8 +86,7 @@ module Karafka
|
|
86
86
|
# @param message [Karafka::Messages::Message]
|
87
87
|
def store_offset(message)
|
88
88
|
@mutex.synchronize do
|
89
|
-
|
90
|
-
@kafka.store_offset(message)
|
89
|
+
internal_store_offset(message)
|
91
90
|
end
|
92
91
|
end
|
93
92
|
|
@@ -104,14 +103,7 @@ module Karafka
|
|
104
103
|
def commit_offsets(async: true)
|
105
104
|
@mutex.lock
|
106
105
|
|
107
|
-
|
108
|
-
|
109
|
-
@kafka.commit(nil, async)
|
110
|
-
@offsetting = false
|
111
|
-
rescue Rdkafka::RdkafkaError => e
|
112
|
-
return if e.code == :no_offset
|
113
|
-
|
114
|
-
raise e
|
106
|
+
internal_commit_offsets(async: async)
|
115
107
|
ensure
|
116
108
|
@mutex.unlock
|
117
109
|
end
|
@@ -128,7 +120,11 @@ module Karafka
|
|
128
120
|
#
|
129
121
|
# @param message [Messages::Message, Messages::Seek] message to which we want to seek to
|
130
122
|
def seek(message)
|
123
|
+
@mutex.lock
|
124
|
+
|
131
125
|
@kafka.seek(message)
|
126
|
+
ensure
|
127
|
+
@mutex.unlock
|
132
128
|
end
|
133
129
|
|
134
130
|
# Pauses given partition and moves back to last successful offset processed.
|
@@ -144,15 +140,17 @@ module Karafka
|
|
144
140
|
# Do not pause if the client got closed, would not change anything
|
145
141
|
return if @closed
|
146
142
|
|
143
|
+
pause_msg = Messages::Seek.new(topic, partition, offset)
|
144
|
+
|
145
|
+
internal_commit_offsets(async: false)
|
146
|
+
|
147
147
|
tpl = topic_partition_list(topic, partition)
|
148
148
|
|
149
149
|
return unless tpl
|
150
150
|
|
151
151
|
@kafka.pause(tpl)
|
152
152
|
|
153
|
-
|
154
|
-
|
155
|
-
seek(pause_msg)
|
153
|
+
@kafka.seek(pause_msg)
|
156
154
|
ensure
|
157
155
|
@mutex.unlock
|
158
156
|
end
|
@@ -166,6 +164,11 @@ module Karafka
|
|
166
164
|
|
167
165
|
return if @closed
|
168
166
|
|
167
|
+
# Always commit synchronously offsets if any when we resume
|
168
|
+
# This prevents resuming without offset in case it would not be committed prior
|
169
|
+
# We can skip performance penalty since resuming should not happen too often
|
170
|
+
internal_commit_offsets(async: false)
|
171
|
+
|
169
172
|
tpl = topic_partition_list(topic, partition)
|
170
173
|
|
171
174
|
return unless tpl
|
@@ -214,11 +217,31 @@ module Karafka
|
|
214
217
|
|
215
218
|
private
|
216
219
|
|
220
|
+
# Non thread-safe offset storing method
|
221
|
+
# @param message [Karafka::Messages::Message]
|
222
|
+
def internal_store_offset(message)
|
223
|
+
@offsetting = true
|
224
|
+
@kafka.store_offset(message)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Non thread-safe message committing method
|
228
|
+
# @param async [Boolean] should the commit happen async or sync (async by default)
|
229
|
+
def internal_commit_offsets(async: true)
|
230
|
+
return unless @offsetting
|
231
|
+
|
232
|
+
@kafka.commit(nil, async)
|
233
|
+
@offsetting = false
|
234
|
+
rescue Rdkafka::RdkafkaError => e
|
235
|
+
return if e.code == :no_offset
|
236
|
+
|
237
|
+
raise e
|
238
|
+
end
|
239
|
+
|
217
240
|
# Commits the stored offsets in a sync way and closes the consumer.
|
218
241
|
def close
|
219
|
-
commit_offsets!
|
220
|
-
|
221
242
|
@mutex.synchronize do
|
243
|
+
internal_commit_offsets(async: false)
|
244
|
+
|
222
245
|
@closed = true
|
223
246
|
|
224
247
|
# Remove callbacks runners that were registered
|
@@ -10,12 +10,18 @@ module Karafka
|
|
10
10
|
class Listener
|
11
11
|
include Helpers::Async
|
12
12
|
|
13
|
+
# Can be useful for logging
|
14
|
+
# @return [String] id of this listener
|
15
|
+
attr_reader :id
|
16
|
+
|
13
17
|
# @param subscription_group [Karafka::Routing::SubscriptionGroup]
|
14
18
|
# @param jobs_queue [Karafka::Processing::JobsQueue] queue where we should push work
|
15
19
|
# @return [Karafka::Connection::Listener] listener instance
|
16
20
|
def initialize(subscription_group, jobs_queue)
|
21
|
+
@id = SecureRandom.uuid
|
17
22
|
@subscription_group = subscription_group
|
18
23
|
@jobs_queue = jobs_queue
|
24
|
+
@jobs_builder = ::Karafka::App.config.internal.jobs_builder
|
19
25
|
@pauses_manager = PausesManager.new
|
20
26
|
@client = Client.new(@subscription_group)
|
21
27
|
@executors = Processing::ExecutorsBuffer.new(@client, subscription_group)
|
@@ -62,16 +68,16 @@ module Karafka
|
|
62
68
|
|
63
69
|
resume_paused_partitions
|
64
70
|
|
65
|
-
# We need to fetch data before we revoke lost partitions details as during the polling
|
66
|
-
# the callbacks for tracking lost partitions are triggered. Otherwise we would be always
|
67
|
-
# one batch behind.
|
68
|
-
poll_and_remap_messages
|
69
|
-
|
70
71
|
Karafka.monitor.instrument(
|
71
72
|
'connection.listener.fetch_loop.received',
|
72
73
|
caller: self,
|
73
74
|
messages_buffer: @messages_buffer
|
74
|
-
)
|
75
|
+
) do
|
76
|
+
# We need to fetch data before we revoke lost partitions details as during the polling
|
77
|
+
# the callbacks for tracking lost partitions are triggered. Otherwise we would be
|
78
|
+
# always one batch behind.
|
79
|
+
poll_and_remap_messages
|
80
|
+
end
|
75
81
|
|
76
82
|
# If there were revoked partitions, we need to wait on their jobs to finish before
|
77
83
|
# distributing consuming jobs as upon revoking, we might get assigned to the same
|
@@ -148,7 +154,7 @@ module Karafka
|
|
148
154
|
partitions.each do |partition|
|
149
155
|
pause_tracker = @pauses_manager.fetch(topic, partition)
|
150
156
|
executor = @executors.fetch(topic, partition, pause_tracker)
|
151
|
-
jobs <<
|
157
|
+
jobs << @jobs_builder.revoked(executor)
|
152
158
|
end
|
153
159
|
end
|
154
160
|
|
@@ -160,7 +166,7 @@ module Karafka
|
|
160
166
|
jobs = []
|
161
167
|
|
162
168
|
@executors.each do |_, _, executor|
|
163
|
-
jobs <<
|
169
|
+
jobs << @jobs_builder.shutdown(executor)
|
164
170
|
end
|
165
171
|
|
166
172
|
@scheduler.schedule_shutdown(@jobs_queue, jobs)
|
@@ -185,13 +191,11 @@ module Karafka
|
|
185
191
|
jobs = []
|
186
192
|
|
187
193
|
@messages_buffer.each do |topic, partition, messages|
|
188
|
-
|
189
|
-
|
190
|
-
next if pause.paused?
|
194
|
+
pause_tracker = @pauses_manager.fetch(topic, partition)
|
191
195
|
|
192
|
-
executor = @executors.fetch(topic, partition,
|
196
|
+
executor = @executors.fetch(topic, partition, pause_tracker)
|
193
197
|
|
194
|
-
jobs <<
|
198
|
+
jobs << @jobs_builder.consume(executor, messages)
|
195
199
|
end
|
196
200
|
|
197
201
|
@scheduler.schedule_consumption(@jobs_queue, jobs)
|
@@ -31,10 +31,11 @@ module Karafka
|
|
31
31
|
# We validate internals just to be sure, that they are present and working
|
32
32
|
required(:internal).schema do
|
33
33
|
required(:routing_builder)
|
34
|
+
required(:subscription_groups_builder)
|
35
|
+
required(:jobs_builder)
|
34
36
|
required(:status)
|
35
37
|
required(:process)
|
36
38
|
required(:scheduler)
|
37
|
-
required(:subscription_groups_builder)
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
@@ -15,16 +15,43 @@ module Karafka
|
|
15
15
|
|
16
16
|
# Logs each messages fetching attempt
|
17
17
|
#
|
18
|
-
# @param
|
19
|
-
def on_connection_listener_fetch_loop(
|
20
|
-
|
18
|
+
# @param event [Dry::Events::Event] event details including payload
|
19
|
+
def on_connection_listener_fetch_loop(event)
|
20
|
+
listener = event[:caller]
|
21
|
+
info "[#{listener.id}] Polling messages..."
|
21
22
|
end
|
22
23
|
|
23
24
|
# Logs about messages that we've received from Kafka
|
24
25
|
#
|
25
26
|
# @param event [Dry::Events::Event] event details including payload
|
26
27
|
def on_connection_listener_fetch_loop_received(event)
|
27
|
-
|
28
|
+
listener = event[:caller]
|
29
|
+
time = event[:time]
|
30
|
+
messages_count = event[:messages_buffer].size
|
31
|
+
info "[#{listener.id}] Polled #{messages_count} messages in #{time}ms"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Prints info about the fact that a given job has started
|
35
|
+
#
|
36
|
+
# @param event [Dry::Events::Event] event details including payload
|
37
|
+
def on_worker_process(event)
|
38
|
+
job = event[:job]
|
39
|
+
job_type = job.class.to_s.split('::').last
|
40
|
+
consumer = job.executor.topic.consumer
|
41
|
+
topic = job.executor.topic.name
|
42
|
+
info "[#{job.id}] #{job_type} job for #{consumer} on #{topic} started"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Prints info about the fact that a given job has finished
|
46
|
+
#
|
47
|
+
# @param event [Dry::Events::Event] event details including payload
|
48
|
+
def on_worker_processed(event)
|
49
|
+
job = event[:job]
|
50
|
+
time = event[:time]
|
51
|
+
job_type = job.class.to_s.split('::').last
|
52
|
+
consumer = job.executor.topic.consumer
|
53
|
+
topic = job.executor.topic.name
|
54
|
+
info "[#{job.id}] #{job_type} job for #{consumer} on #{topic} finished in #{time}ms"
|
28
55
|
end
|
29
56
|
|
30
57
|
# Logs info about system signals that Karafka received.
|
@@ -52,16 +79,14 @@ module Karafka
|
|
52
79
|
#
|
53
80
|
# @param _event [Dry::Events::Event] event details including payload
|
54
81
|
def on_app_stopping(_event)
|
55
|
-
|
56
|
-
Thread.new { info 'Stopping Karafka server' }
|
82
|
+
info 'Stopping Karafka server'
|
57
83
|
end
|
58
84
|
|
59
85
|
# Logs info that we stopped the Karafka server.
|
60
86
|
#
|
61
87
|
# @param _event [Dry::Events::Event] event details including payload
|
62
88
|
def on_app_stopped(_event)
|
63
|
-
|
64
|
-
Thread.new { info 'Stopped Karafka server' }
|
89
|
+
info 'Stopped Karafka server'
|
65
90
|
end
|
66
91
|
|
67
92
|
# There are many types of errors that can occur in many places, but we provide a single
|
@@ -73,6 +98,9 @@ module Karafka
|
|
73
98
|
details = (error.backtrace || []).join("\n")
|
74
99
|
|
75
100
|
case type
|
101
|
+
when 'consumer.prepared.error'
|
102
|
+
error "Consumer prepared error: #{error}"
|
103
|
+
error details
|
76
104
|
when 'consumer.consume.error'
|
77
105
|
error "Consumer consuming error: #{error}"
|
78
106
|
error details
|
@@ -95,8 +123,7 @@ module Karafka
|
|
95
123
|
fatal "Runner crashed due to an error: #{error}"
|
96
124
|
fatal details
|
97
125
|
when 'app.stopping.error'
|
98
|
-
|
99
|
-
Thread.new { error 'Forceful Karafka server stop' }
|
126
|
+
error 'Forceful Karafka server stop'
|
100
127
|
when 'librdkafka.error'
|
101
128
|
error "librdkafka internal error occurred: #{error}"
|
102
129
|
error details
|