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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile.lock +4 -4
  5. data/bin/integrations +36 -14
  6. data/bin/scenario +29 -0
  7. data/docker-compose.yml +2 -0
  8. data/karafka.gemspec +1 -1
  9. data/lib/karafka/active_job/routing/extensions.rb +11 -1
  10. data/lib/karafka/base_consumer.rb +41 -26
  11. data/lib/karafka/connection/client.rb +38 -15
  12. data/lib/karafka/connection/listener.rb +17 -13
  13. data/lib/karafka/contracts/config.rb +2 -1
  14. data/lib/karafka/instrumentation/logger_listener.rb +37 -10
  15. data/lib/karafka/instrumentation/monitor.rb +3 -0
  16. data/lib/karafka/licenser.rb +26 -7
  17. data/lib/karafka/pro/active_job/consumer.rb +36 -9
  18. data/lib/karafka/pro/active_job/dispatcher.rb +9 -9
  19. data/lib/karafka/pro/active_job/job_options_contract.rb +9 -9
  20. data/lib/karafka/pro/base_consumer_extensions.rb +66 -0
  21. data/lib/karafka/pro/loader.rb +29 -15
  22. data/lib/karafka/pro/performance_tracker.rb +9 -9
  23. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +9 -10
  24. data/lib/karafka/pro/processing/jobs_builder.rb +31 -0
  25. data/lib/karafka/pro/routing/extensions.rb +32 -0
  26. data/lib/karafka/pro/scheduler.rb +9 -9
  27. data/lib/karafka/processing/executor.rb +8 -1
  28. data/lib/karafka/processing/jobs_builder.rb +28 -0
  29. data/lib/karafka/processing/worker.rb +17 -9
  30. data/lib/karafka/routing/subscription_group.rb +1 -1
  31. data/lib/karafka/setup/config.rb +3 -1
  32. data/lib/karafka/status.rb +1 -3
  33. data/lib/karafka/version.rb +1 -1
  34. data.tar.gz.sig +0 -0
  35. metadata +8 -2
  36. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f75623e7d9cdcc4ba151ad551079275528c56bf66cd9c32ecc585756a8d505c
4
- data.tar.gz: e0becf53133b579f581ddfdf947bbff21221fe69a8c73a0406174aecd0155f3a
3
+ metadata.gz: 86b352cc7737bde8484567662d546165793496adb6a923e33aa7e4823e4a456f
4
+ data.tar.gz: 2a180ba7b177db9f4c67de387262d47ac3eb2580860a548992912cd89a049da7
5
5
  SHA512:
6
- metadata.gz: 2ef2ac59f1ea60136abbaccf460206a0c2f6d4fe3124eda520f3a19568702acc774e0d9e02eae24cfe6bb6cb8ee8aa74602588aa818dc3537cd6bbc8409f159d
7
- data.tar.gz: e29e964e777e2bd8a551458f591b92aea69a3ac2eafa1bc1d75bb42d7cd6bb904abf997724be4268ae1f9d429627d7b92d5c38e51817d36b0f27c6499a062af3
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.beta2)
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.1)
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.0)
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.9, >= 1.9.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
- CONCURRENCY = 4
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 = 60 * 2
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
- Process.kill('TERM', pid)
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
- result = success? ? "\e[#{32}m#{'OK'}\e[0m" : "\e[#{31}m#{'FAILED'}\e[0m"
110
-
111
- puts "#{result} #{name}"
112
-
113
- unless success?
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
- # Fail all if any of the tests does not have expected exit code
208
- raise IntegrationTestError unless finished_scenarios.all?(&:success?)
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
@@ -17,6 +17,8 @@ services:
17
17
  "integrations_0_02:2:1,\
18
18
  integrations_1_02:2:1,\
19
19
  integrations_2_02:2:1,\
20
+ integrations_3_02:2:1,\
21
+ integrations_4_02:2:1,\
20
22
  integrations_0_03:3:1,\
21
23
  integrations_1_03:3:1,\
22
24
  integrations_2_03:3:1,\
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.license = 'LGPL-3.0'
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
- def active_job_topic(name)
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
- # Can be used to run preparation code
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 prepared; end
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
- client.resume(
159
- messages.metadata.topic,
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
- @offsetting = true
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
- return unless @offsetting
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
- pause_msg = Messages::Seek.new(topic, partition, offset)
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 << Processing::Jobs::Revoked.new(executor)
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 << Processing::Jobs::Shutdown.new(executor)
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
- pause = @pauses_manager.fetch(topic, partition)
189
-
190
- next if pause.paused?
194
+ pause_tracker = @pauses_manager.fetch(topic, partition)
191
195
 
192
- executor = @executors.fetch(topic, partition, pause)
196
+ executor = @executors.fetch(topic, partition, pause_tracker)
193
197
 
194
- jobs << Processing::Jobs::Consume.new(executor, messages)
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 _event [Dry::Events::Event] event details including payload
19
- def on_connection_listener_fetch_loop(_event)
20
- info 'Receiving new messages from Kafka...'
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
- info "Received #{event[:messages_buffer].size} new messages from Kafka"
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
- # We use a separate thread as logging can't be called from trap context
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
- # We use a separate thread as logging can't be called from trap context
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
- # We use a separate thread as logging can't be called from trap context
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
@@ -33,6 +33,9 @@ module Karafka
33
33
  connection.listener.fetch_loop
34
34
  connection.listener.fetch_loop.received
35
35
 
36
+ worker.process
37
+ worker.processed
38
+
36
39
  statistics.emitted
37
40
 
38
41
  error.occurred