karafka 2.0.40 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +2 -2
  4. data/CHANGELOG.md +30 -1
  5. data/Gemfile +3 -2
  6. data/Gemfile.lock +13 -1
  7. data/bin/integrations +17 -2
  8. data/config/locales/errors.yml +10 -0
  9. data/config/locales/pro_errors.yml +0 -2
  10. data/lib/karafka/active_job/consumer.rb +16 -11
  11. data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
  12. data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
  13. data/lib/karafka/active_job/current_attributes.rb +42 -0
  14. data/lib/karafka/active_job/dispatcher.rb +8 -2
  15. data/lib/karafka/admin.rb +17 -13
  16. data/lib/karafka/connection/client.rb +6 -3
  17. data/lib/karafka/errors.rb +3 -0
  18. data/lib/karafka/instrumentation/callbacks/statistics.rb +12 -0
  19. data/lib/karafka/instrumentation/logger_listener.rb +16 -5
  20. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +166 -0
  21. data/lib/karafka/pro/active_job/consumer.rb +1 -10
  22. data/lib/karafka/pro/active_job/dispatcher.rb +2 -2
  23. data/lib/karafka/pro/iterator.rb +253 -0
  24. data/lib/karafka/pro/processing/coordinator.rb +20 -1
  25. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
  26. data/lib/karafka/pro/processing/filters_applier.rb +4 -0
  27. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +1 -1
  28. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +3 -1
  29. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +2 -2
  30. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +2 -0
  31. data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +1 -1
  32. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +1 -1
  33. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +3 -6
  34. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
  35. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +1 -0
  36. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +3 -7
  37. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
  38. data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +1 -0
  39. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +3 -6
  40. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
  41. data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +1 -0
  42. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +8 -7
  43. data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
  44. data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -0
  45. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
  46. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +2 -0
  47. data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
  48. data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
  49. data/lib/karafka/pro/{base_consumer.rb → processing/strategies/mom/vp.rb} +17 -7
  50. data/lib/karafka/pro/processing/strategies/vp/default.rb +51 -0
  51. data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
  52. data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +0 -17
  53. data/lib/karafka/processing/strategies/default.rb +2 -0
  54. data/lib/karafka/processing/strategies/dlq_mom.rb +9 -7
  55. data/lib/karafka/routing/router.rb +15 -0
  56. data/lib/karafka/setup/config.rb +7 -1
  57. data/lib/karafka/version.rb +1 -1
  58. data/lib/karafka.rb +5 -0
  59. data.tar.gz.sig +0 -0
  60. metadata +17 -4
  61. metadata.gz.sig +0 -0
  62. data/lib/karafka/instrumentation/vendors/datadog/listener.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a4dd6f2d6a1354ac085a5f7ff7de13ca8589d9e83f7fffe2f83c2e4501895a6
4
- data.tar.gz: 55426141e6974b329f8e207e20d3acd3935ca025a58b3f1fc287be3c363dee8f
3
+ metadata.gz: b3d0a2f78b4bf7fa8f49527d48d2e877b95597566e07beabf0166a02259a936b
4
+ data.tar.gz: fc6054ad5f99bfe8a678c337167f93fc612dddfe88494f8891158dbd8610fb7f
5
5
  SHA512:
6
- metadata.gz: d5bdbee5398cc6230d31d6915aef359071f68536b89bb7661ce3323a61b926966fe045fd15d3b19b648478a82ce631692302da63e2ccadc8c7c3b476649ae10e
7
- data.tar.gz: 924af69aee02bb6a726e09d7f277c8a79dc18eccef6dccf51e62090dc956584ac242bb30b90d0edaec2c626d02960b445ed52e2142b34c20b8b59ce1339b0469
6
+ metadata.gz: 0fb1fa88ef76ce81e145797a1364ac36bea2b94c47e733856cfd5ec9b37d0d9e2e984a3e4ef7fc36d2ac34c448e490cfdea4e10fae886cd80fb289798e55d308
7
+ data.tar.gz: 68df2bc1edb9acccd45d32428b43fd5dee12b9333c6059c801aa4ac03b3b89c01e93e8ea4ebbb8021618447c1122c8c5df64afc86747d6f0deec0cf992237e82
checksums.yaml.gz.sig CHANGED
Binary file
@@ -62,7 +62,7 @@ jobs:
62
62
  run: \curl -sSL https://api.coditsu.io/run/ci | bash
63
63
 
64
64
  specs:
65
- timeout-minutes: 45
65
+ timeout-minutes: 30
66
66
  runs-on: ubuntu-latest
67
67
  needs: diffend
68
68
  strategy:
@@ -102,7 +102,7 @@ jobs:
102
102
  run: bin/rspecs
103
103
 
104
104
  integrations:
105
- timeout-minutes: 30
105
+ timeout-minutes: 45
106
106
  runs-on: ubuntu-latest
107
107
  needs: diffend
108
108
  strategy:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Karafka framework changelog
2
2
 
3
+ ## 2.1.0 (2023-05-22)
4
+ - **[Feature]** Provide ability to use CurrentAttributes with ActiveJob's Karafka adapter.
5
+ - **[Feature]** Introduce collective Virtual Partitions offset management.
6
+ - **[Feature]** Use virtual offsets to filter out messages that would be re-processed upon retries.
7
+ - [Improvement] No longer break processing on failing parallel virtual partitions in ActiveJob because it is compensated by virtual marking.
8
+ - [Improvement] Always use Virtual offset management for Pro ActiveJobs.
9
+ - [Improvement] Do not attempt to mark offsets on already revoked partitions.
10
+ - [Improvement] Make sure, that VP components are not injected into non VP strategies.
11
+ - [Improvement] Improve complex strategies inheritance flow.
12
+ - [Improvement] Optimize offset management for DLQ + MoM feature combinations.
13
+ - [Change] Removed `Karafka::Pro::BaseConsumer` in favor of `Karafka::BaseConsumer`. (#1345)
14
+ - [Fix] Fix for `max_messages` and `max_wait_time` not having reference in errors.yml (#1443)
15
+
16
+ ### Upgrade notes
17
+
18
+ 1. Upgrade to Karafka `2.0.41` prior to upgrading to `2.1.0`.
19
+ 2. Replace `Karafka::Pro::BaseConsumer` references to `Karafka::BaseConsumer`.
20
+ 3. Replace `Karafka::Instrumentation::Vendors::Datadog:Listener` with `Karafka::Instrumentation::Vendors::Datadog::MetricsListener`.
21
+
22
+ ## 2.0.41 (2023-14-19)
23
+ - **[Feature]** Provide `Karafka::Pro::Iterator` for anonymous topic/partitions iterations and messages lookups (#1389 and #1427).
24
+ - [Improvement] Optimize topic lookup for `read_topic` admin method usage.
25
+ - [Improvement] Report via `LoggerListener` information about the partition on which a given job has started and finished.
26
+ - [Improvement] Slightly normalize the `LoggerListener` format. Always report partition related operations as followed: `TOPIC_NAME/PARTITION`.
27
+ - [Improvement] Do not retry recovery from `unknown_topic_or_part` when Karafka is shutting down as there is no point and no risk of any data losses.
28
+ - [Improvement] Report `client.software.name` and `client.software.version` according to `librdkafka` recommendation.
29
+ - [Improvement] Report ten longest integration specs after the suite execution.
30
+ - [Improvement] Prevent user originating errors related to statistics processing after listener loop crash from potentially crashing the listener loop and hanging Karafka process.
31
+
3
32
  ## 2.0.40 (2023-04-13)
4
33
  - [Improvement] Introduce `Karafka::Messages::Messages#empty?` method to handle Idle related cases where shutdown or revocation would be called on an empty messages set. This method allows for checking if there are any messages in the messages batch.
5
34
  - [Refactor] Require messages builder to accept partition and do not fetch it from messages.
@@ -50,7 +79,7 @@
50
79
 
51
80
  ## 2.0.35 (2023-03-13)
52
81
  - **[Feature]** Allow for defining topics config via the DSL and its automatic creation via CLI command.
53
- - **[Feature]** Allow for full topics reset and topics repartitioning via the CLI.
82
+ - **[Feature]** Allow for full topics reset and topics repartitioning via the CLI.
54
83
 
55
84
  ## 2.0.34 (2023-03-04)
56
85
  - [Improvement] Attach an `embedded` tag to Karafka processes started using the embedded API.
data/Gemfile CHANGED
@@ -6,10 +6,11 @@ plugin 'diffend'
6
6
 
7
7
  gemspec
8
8
 
9
- # Karafka gem does not require this but we add it here so we can test the integration with
10
- # ActiveJob much easier
9
+ # Karafka gem does not require activejob nor karafka-web to work
10
+ # They are added here because they are part of the integration suite
11
11
  group :integrations do
12
12
  gem 'activejob'
13
+ gem 'karafka-web'
13
14
  end
14
15
 
15
16
  group :test do
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka (2.0.40)
4
+ karafka (2.1.0)
5
5
  karafka-core (>= 2.0.12, < 3.0.0)
6
6
  thor (>= 0.20)
7
7
  waterdrop (>= 2.4.10, < 3.0.0)
@@ -22,6 +22,7 @@ GEM
22
22
  concurrent-ruby (1.2.2)
23
23
  diff-lcs (1.5.0)
24
24
  docile (1.4.0)
25
+ erubi (1.12.0)
25
26
  factory_bot (6.2.1)
26
27
  activesupport (>= 5.0.0)
27
28
  ffi (1.15.5)
@@ -36,9 +37,18 @@ GEM
36
37
  ffi (~> 1.15)
37
38
  mini_portile2 (~> 2.6)
38
39
  rake (> 12)
40
+ karafka-web (0.5.1)
41
+ erubi (~> 1.4)
42
+ karafka (>= 2.0.40, < 3.0.0)
43
+ karafka-core (>= 2.0.12, < 3.0.0)
44
+ roda (~> 3.63)
45
+ tilt (~> 2.0)
39
46
  mini_portile2 (2.8.1)
40
47
  minitest (5.18.0)
48
+ rack (3.0.7)
41
49
  rake (13.0.6)
50
+ roda (3.67.0)
51
+ rack
42
52
  rspec (3.12.0)
43
53
  rspec-core (~> 3.12.0)
44
54
  rspec-expectations (~> 3.12.0)
@@ -59,6 +69,7 @@ GEM
59
69
  simplecov-html (0.12.3)
60
70
  simplecov_json_formatter (0.1.4)
61
71
  thor (1.2.1)
72
+ tilt (2.1.0)
62
73
  tzinfo (2.0.6)
63
74
  concurrent-ruby (~> 1.0)
64
75
  waterdrop (2.5.1)
@@ -75,6 +86,7 @@ DEPENDENCIES
75
86
  byebug
76
87
  factory_bot
77
88
  karafka!
89
+ karafka-web
78
90
  rspec
79
91
  simplecov
80
92
 
data/bin/integrations CHANGED
@@ -152,8 +152,14 @@ class Scenario
152
152
  end
153
153
  end
154
154
 
155
+ # @return [Float] number of seconds that a given spec took to run
156
+ def time_taken
157
+ @finished_at - @started_at
158
+ end
159
+
155
160
  # Close all the files that are open, so they do not pile up
156
161
  def close
162
+ @finished_at = current_time
157
163
  @stdin.close
158
164
  @stdout.close
159
165
  @stderr.close
@@ -262,13 +268,22 @@ while finished_scenarios.size < scenarios.size
262
268
  sleep(0.1)
263
269
  end
264
270
 
271
+ # Report longest scenarios
272
+ puts
273
+ puts "\nLongest scenarios:\n\n"
274
+
275
+ finished_scenarios.sort_by(&:time_taken).reverse.first(10).each do |long_scenario|
276
+ puts "[#{'%6.2f' % long_scenario.time_taken}] #{long_scenario.name}"
277
+ end
278
+
265
279
  failed_scenarios = finished_scenarios.reject(&:success?)
266
280
 
267
- # Report once more on the failed jobs
268
- # This will only list scenarios that failed without printing their stdout here.
269
281
  if failed_scenarios.empty?
270
282
  puts
271
283
  else
284
+ # Report once more on the failed jobs
285
+ # This will only list scenarios that failed without printing their stdout here.
286
+ puts
272
287
  puts "\nFailed scenarios:\n\n"
273
288
 
274
289
  failed_scenarios.each do |scenario|
@@ -15,6 +15,13 @@ en:
15
15
  shutdown_timeout_format: needs to be an integer bigger than 0
16
16
  max_wait_time_format: needs to be an integer bigger than 0
17
17
  kafka_format: needs to be a filled hash
18
+ internal.processing.jobs_builder_format: cannot be nil
19
+ internal.processing.scheduler: cannot be nil
20
+ internal.processing.coordinator_class: cannot be nil
21
+ internal.processing.partitioner_class: cannot be nil
22
+ internal.active_job.dispatcher: cannot be nil
23
+ internal.active_job.job_options_contract: cannot be nil
24
+ internal.active_job.consumer_class: cannot be nil
18
25
  internal.status_format: needs to be present
19
26
  internal.process_format: needs to be present
20
27
  internal.routing.builder_format: needs to be present
@@ -31,7 +38,10 @@ en:
31
38
  topics_missing: No topics to subscribe to
32
39
 
33
40
  topic:
41
+ kafka: needs to be a hash with kafka scope settings details
34
42
  missing: needs to be present
43
+ max_messages_format: 'needs to be an integer bigger than 0'
44
+ max_wait_time_format: 'needs to be an integer bigger than 0'
35
45
  name_format: 'needs to be a string with a Kafka accepted format'
36
46
  deserializer_format: needs to be present
37
47
  consumer_format: needs to be present
@@ -4,8 +4,6 @@ en:
4
4
  virtual_partitions.partitioner_respond_to_call: needs to be defined and needs to respond to `#call`
5
5
  virtual_partitions.max_partitions_format: needs to be equal or more than 1
6
6
 
7
- manual_offset_management_not_with_virtual_partitions: cannot be used together with Virtual Partitions
8
-
9
7
  long_running_job.active_format: needs to be either true or false
10
8
 
11
9
  dead_letter_queue_with_virtual_partitions: when using Dead Letter Queue with Virtual Partitions, at least one retry is required.
@@ -24,21 +24,26 @@ module Karafka
24
24
  #
25
25
  # @param job_message [Karafka::Messages::Message] message with active job
26
26
  def consume_job(job_message)
27
- # We technically speaking could set this as deserializer and reference it from the
28
- # message instead of using the `#raw_payload`. This is not done on purpose to simplify
29
- # the ActiveJob setup here
30
- job = ::ActiveSupport::JSON.decode(job_message.raw_payload)
27
+ with_deserialized_job(job_message) do |job|
28
+ tags.add(:job_class, job['job_class'])
31
29
 
32
- tags.add(:job_class, job['job_class'])
30
+ payload = { caller: self, job: job, message: job_message }
33
31
 
34
- payload = { caller: self, job: job, message: job_message }
35
-
36
- # We publish both to make it consistent with `consumer.x` events
37
- Karafka.monitor.instrument('active_job.consume', payload)
38
- Karafka.monitor.instrument('active_job.consumed', payload) do
39
- ::ActiveJob::Base.execute(job)
32
+ # We publish both to make it consistent with `consumer.x` events
33
+ Karafka.monitor.instrument('active_job.consume', payload)
34
+ Karafka.monitor.instrument('active_job.consumed', payload) do
35
+ ::ActiveJob::Base.execute(job)
36
+ end
40
37
  end
41
38
  end
39
+
40
+ # @param job_message [Karafka::Messages::Message] message with active job
41
+ def with_deserialized_job(job_message)
42
+ # We technically speaking could set this as deserializer and reference it from the
43
+ # message instead of using the `#raw_payload`. This is not done on purpose to simplify
44
+ # the ActiveJob setup here
45
+ yield ::ActiveSupport::JSON.decode(job_message.raw_payload)
46
+ end
42
47
  end
43
48
  end
44
49
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module ActiveJob
5
+ module CurrentAttributes
6
+ # Module expanding the job deserialization to extract current attributes and load them
7
+ # for the time of the job execution
8
+ module Loading
9
+ # @param job_message [Karafka::Messages::Message] message with active job
10
+ def with_deserialized_job(job_message)
11
+ super(job_message) do |job|
12
+ resetable = []
13
+
14
+ _cattr_klasses.each do |key, cattr_klass_str|
15
+ next unless job.key?(key)
16
+
17
+ attributes = job.delete(key)
18
+
19
+ cattr_klass = cattr_klass_str.constantize
20
+
21
+ attributes.each do |name, value|
22
+ cattr_klass.public_send("#{name}=", value)
23
+ end
24
+
25
+ resetable << cattr_klass
26
+ end
27
+
28
+ yield(job)
29
+
30
+ resetable.each(&:reset)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module ActiveJob
5
+ module CurrentAttributes
6
+ # Module adding the current attributes persistence into the ActiveJob jobs
7
+ module Persistence
8
+ # Alters the job serialization to inject the current attributes into the json before we
9
+ # send it to Kafka
10
+ #
11
+ # @param job [ActiveJob::Base] job
12
+ def serialize_job(job)
13
+ json = super(job)
14
+
15
+ _cattr_klasses.each do |key, cattr_klass_str|
16
+ next if json.key?(key)
17
+
18
+ attrs = cattr_klass_str.constantize.attributes
19
+
20
+ json[key] = attrs unless attrs.empty?
21
+ end
22
+
23
+ json
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/current_attributes'
4
+ require_relative 'current_attributes/loading'
5
+ require_relative 'current_attributes/persistence'
6
+
7
+ # This code is based on Sidekiqs approach to persisting current attributes
8
+ # @see https://github.com/sidekiq/sidekiq/blob/main/lib/sidekiq/middleware/current_attributes.rb
9
+ module Karafka
10
+ module ActiveJob
11
+ # Module that allows to persist current attributes on Karafka jobs
12
+ module CurrentAttributes
13
+ # Allows for persistence of given current attributes via AJ + Karafka
14
+ #
15
+ # @param klasses [Array<String, Class>] classes or names of the current attributes classes
16
+ def persist(*klasses)
17
+ # Support for providing multiple classes
18
+ klasses = Array(klasses).flatten
19
+
20
+ [Dispatcher, Consumer]
21
+ .reject { |expandable| expandable.respond_to?(:_cattr_klasses) }
22
+ .each { |expandable| expandable.class_attribute :_cattr_klasses, default: {} }
23
+
24
+ # Do not double inject in case of running persist multiple times
25
+ Dispatcher.prepend(Persistence) unless Dispatcher.ancestors.include?(Persistence)
26
+ Consumer.prepend(Loading) unless Consumer.ancestors.include?(Loading)
27
+
28
+ klasses.map(&:to_s).each do |stringified_klass|
29
+ # Prevent registering same klass multiple times
30
+ next if Dispatcher._cattr_klasses.value?(stringified_klass)
31
+
32
+ key = "cattr_#{Dispatcher._cattr_klasses.count}"
33
+
34
+ Dispatcher._cattr_klasses[key] = stringified_klass
35
+ Consumer._cattr_klasses[key] = stringified_klass
36
+ end
37
+ end
38
+
39
+ module_function :persist
40
+ end
41
+ end
42
+ end
@@ -18,7 +18,7 @@ module Karafka
18
18
  ::Karafka.producer.public_send(
19
19
  fetch_option(job, :dispatch_method, DEFAULTS),
20
20
  topic: job.queue_name,
21
- payload: ::ActiveSupport::JSON.encode(job.serialize)
21
+ payload: ::ActiveSupport::JSON.encode(serialize_job(job))
22
22
  )
23
23
  end
24
24
 
@@ -34,7 +34,7 @@ module Karafka
34
34
 
35
35
  dispatches[d_method] << {
36
36
  topic: job.queue_name,
37
- payload: ::ActiveSupport::JSON.encode(job.serialize)
37
+ payload: ::ActiveSupport::JSON.encode(serialize_job(job))
38
38
  }
39
39
  end
40
40
 
@@ -58,6 +58,12 @@ module Karafka
58
58
  .karafka_options
59
59
  .fetch(key, defaults.fetch(key))
60
60
  end
61
+
62
+ # @param job [ActiveJob::Base] job
63
+ # @return [Hash] json representation of the job
64
+ def serialize_job(job)
65
+ job.serialize
66
+ end
61
67
  end
62
68
  end
63
69
  end
data/lib/karafka/admin.rb CHANGED
@@ -96,13 +96,15 @@ module Karafka
96
96
  end
97
97
  end
98
98
 
99
+ # Use topic from routes if we can match it or create a dummy one
100
+ # Dummy one is used in case we cannot match the topic with routes. This can happen
101
+ # when admin API is used to read topics that are not part of the routing
102
+ topic = ::Karafka::Routing::Router.find_or_initialize_by_name(name)
103
+
99
104
  messages.map! do |message|
100
105
  Messages::Builders::Message.call(
101
106
  message,
102
- # Use topic from routes if we can match it or create a dummy one
103
- # Dummy one is used in case we cannot match the topic with routes. This can happen
104
- # when admin API is used to read topics that are not part of the routing
105
- Routing::Router.find_by(name: name) || Topic.new(name, App.config.deserializer),
107
+ topic,
106
108
  Time.now
107
109
  )
108
110
  end
@@ -173,6 +175,17 @@ module Karafka
173
175
  end
174
176
  end
175
177
 
178
+ # Creates consumer instance and yields it. After usage it closes the consumer instance
179
+ # This API can be used in other pieces of code and allows for low-level consumer usage
180
+ #
181
+ # @param settings [Hash] extra settings to customize consumer
182
+ def with_consumer(settings = {})
183
+ consumer = config(:consumer, settings).consumer
184
+ yield(consumer)
185
+ ensure
186
+ consumer&.close
187
+ end
188
+
176
189
  private
177
190
 
178
191
  # @return [Array<String>] topics names
@@ -195,15 +208,6 @@ module Karafka
195
208
  admin&.close
196
209
  end
197
210
 
198
- # Creates consumer instance and yields it. After usage it closes the consumer instance
199
- # @param settings [Hash] extra settings to customize consumer
200
- def with_consumer(settings = {})
201
- consumer = config(:consumer, settings).consumer
202
- yield(consumer)
203
- ensure
204
- consumer&.close
205
- end
206
-
207
211
  # There are some cases where rdkafka admin operations finish successfully but without the
208
212
  # callback being triggered to materialize the post-promise object. Until this is fixed we
209
213
  # can figure out, that operation we wanted to do finished successfully by checking that the
@@ -30,7 +30,7 @@ module Karafka
30
30
  #
31
31
  # @param subscription_group [Karafka::Routing::SubscriptionGroup] subscription group
32
32
  # with all the configuration details needed for us to create a client
33
- # @return [Karafka::Connection::Rdk::Consumer]
33
+ # @return [Karafka::Connection::Client]
34
34
  def initialize(subscription_group)
35
35
  @id = SecureRandom.hex(6)
36
36
  # Name is set when we build consumer
@@ -369,6 +369,8 @@ module Karafka
369
369
  rescue ::Rdkafka::RdkafkaError => e
370
370
  early_report = false
371
371
 
372
+ retryable = time_poll.attempts <= MAX_POLL_RETRIES && time_poll.retryable?
373
+
372
374
  # There are retryable issues on which we want to report fast as they are source of
373
375
  # problems and can mean some bigger system instabilities
374
376
  # Those are mainly network issues and exceeding the max poll interval
@@ -389,9 +391,10 @@ module Karafka
389
391
  return nil if @subscription_group.kafka[:'allow.auto.create.topics']
390
392
 
391
393
  early_report = true
392
- end
393
394
 
394
- retryable = time_poll.attempts <= MAX_POLL_RETRIES && time_poll.retryable?
395
+ # No sense in retrying when no topic/partition and we're no longer running
396
+ retryable = false unless Karafka::App.running?
397
+ end
395
398
 
396
399
  if early_report || !retryable
397
400
  Karafka.monitor.instrument(
@@ -46,5 +46,8 @@ module Karafka
46
46
 
47
47
  # This should never happen. Please open an issue if it does.
48
48
  StrategyNotFoundError = Class.new(BaseError)
49
+
50
+ # This should never happen. Please open an issue if it does.
51
+ InvalidRealOffsetUsage = Class.new(BaseError)
49
52
  end
50
53
  end
@@ -32,6 +32,18 @@ module Karafka
32
32
  consumer_group_id: @consumer_group_id,
33
33
  statistics: @statistics_decorator.call(statistics)
34
34
  )
35
+ # We need to catch and handle any potential errors coming from the instrumentation pipeline
36
+ # as otherwise, in case of statistics which run in the main librdkafka thread, any crash
37
+ # will hang the whole process.
38
+ rescue StandardError => e
39
+ ::Karafka.monitor.instrument(
40
+ 'error.occurred',
41
+ caller: self,
42
+ subscription_group_id: @subscription_group_id,
43
+ consumer_group_id: @consumer_group_id,
44
+ type: 'statistics.emitted.error',
45
+ error: e
46
+ )
35
47
  end
36
48
  end
37
49
  end
@@ -48,7 +48,8 @@ module Karafka
48
48
  job_type = job.class.to_s.split('::').last
49
49
  consumer = job.executor.topic.consumer
50
50
  topic = job.executor.topic.name
51
- info "[#{job.id}] #{job_type} job for #{consumer} on #{topic} started"
51
+ partition = job.executor.partition
52
+ info "[#{job.id}] #{job_type} job for #{consumer} on #{topic}/#{partition} started"
52
53
  end
53
54
 
54
55
  # Prints info about the fact that a given job has finished
@@ -60,7 +61,11 @@ module Karafka
60
61
  job_type = job.class.to_s.split('::').last
61
62
  consumer = job.executor.topic.consumer
62
63
  topic = job.executor.topic.name
63
- info "[#{job.id}] #{job_type} job for #{consumer} on #{topic} finished in #{time}ms"
64
+ partition = job.executor.partition
65
+ info <<~MSG.tr("\n", ' ').strip!
66
+ [#{job.id}] #{job_type} job for #{consumer}
67
+ on #{topic}/#{partition} finished in #{time}ms
68
+ MSG
64
69
  end
65
70
 
66
71
  # Prints info about a consumer pause occurrence. Irrelevant if user or system initiated.
@@ -73,7 +78,7 @@ module Karafka
73
78
  client = event[:caller]
74
79
 
75
80
  info <<~MSG.tr("\n", ' ').strip!
76
- [#{client.id}] Pausing partition #{partition} of topic #{topic} on offset #{offset}
81
+ [#{client.id}] Pausing on topic #{topic}/#{partition} on offset #{offset}
77
82
  MSG
78
83
  end
79
84
 
@@ -86,7 +91,7 @@ module Karafka
86
91
  client = event[:caller]
87
92
 
88
93
  info <<~MSG.tr("\n", ' ').strip!
89
- [#{client.id}] Resuming partition #{partition} of topic #{topic}
94
+ [#{client.id}] Resuming on topic #{topic}/#{partition}
90
95
  MSG
91
96
  end
92
97
 
@@ -102,7 +107,7 @@ module Karafka
102
107
 
103
108
  info <<~MSG.tr("\n", ' ').strip!
104
109
  [#{consumer.id}] Retrying of #{consumer.class} after #{timeout} ms
105
- on partition #{partition} of topic #{topic} from offset #{offset}
110
+ on topic #{topic}/#{partition} from offset #{offset}
106
111
  MSG
107
112
  end
108
113
 
@@ -261,6 +266,12 @@ module Karafka
261
266
  when 'librdkafka.error'
262
267
  error "librdkafka internal error occurred: #{error}"
263
268
  error details
269
+ # Those can occur when emitted statistics are consumed by the end user and the processing
270
+ # of statistics fails. The statistics are emitted from librdkafka main loop thread and
271
+ # any errors there crash the whole thread
272
+ when 'statistics.emitted.error'
273
+ error "statistics.emitted processing failed due to an error: #{error}"
274
+ error details
264
275
  # Those will only occur when retries in the client fail and when they did not stop after
265
276
  # back-offs
266
277
  when 'connection.client.poll.error'