karafka 2.5.2 → 2.5.4.rc1

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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/config/locales/errors.yml +14 -0
  4. data/karafka.gemspec +15 -4
  5. data/lib/active_job/queue_adapters/karafka_adapter.rb +2 -2
  6. data/lib/karafka/active_job/consumer.rb +2 -2
  7. data/lib/karafka/active_job/current_attributes.rb +2 -2
  8. data/lib/karafka/active_job/deserializer.rb +1 -1
  9. data/lib/karafka/active_job/dispatcher.rb +2 -2
  10. data/lib/karafka/admin/configs/resource.rb +7 -1
  11. data/lib/karafka/admin/consumer_groups.rb +6 -8
  12. data/lib/karafka/admin/contracts/replication.rb +149 -0
  13. data/lib/karafka/admin/replication.rb +462 -0
  14. data/lib/karafka/admin/topics.rb +5 -4
  15. data/lib/karafka/admin.rb +57 -12
  16. data/lib/karafka/app.rb +3 -3
  17. data/lib/karafka/base_consumer.rb +1 -1
  18. data/lib/karafka/cli/base.rb +1 -1
  19. data/lib/karafka/cli/console.rb +1 -1
  20. data/lib/karafka/cli/contracts/server.rb +1 -1
  21. data/lib/karafka/cli/help.rb +1 -1
  22. data/lib/karafka/cli/install.rb +2 -1
  23. data/lib/karafka/cli/server.rb +1 -1
  24. data/lib/karafka/cli/swarm.rb +1 -1
  25. data/lib/karafka/connection/client.rb +19 -18
  26. data/lib/karafka/connection/manager.rb +1 -0
  27. data/lib/karafka/connection/proxy.rb +1 -1
  28. data/lib/karafka/connection/rebalance_manager.rb +1 -1
  29. data/lib/karafka/connection/status.rb +1 -0
  30. data/lib/karafka/constraints.rb +1 -1
  31. data/lib/karafka/contracts/base.rb +1 -1
  32. data/lib/karafka/deserializers/payload.rb +1 -1
  33. data/lib/karafka/helpers/async.rb +1 -1
  34. data/lib/karafka/helpers/config_importer.rb +3 -3
  35. data/lib/karafka/helpers/multi_delegator.rb +3 -0
  36. data/lib/karafka/instrumentation/assignments_tracker.rb +2 -1
  37. data/lib/karafka/instrumentation/callbacks/error.rb +2 -2
  38. data/lib/karafka/instrumentation/callbacks/statistics.rb +3 -3
  39. data/lib/karafka/instrumentation/logger.rb +6 -6
  40. data/lib/karafka/instrumentation/logger_listener.rb +0 -2
  41. data/lib/karafka/instrumentation/monitor.rb +2 -2
  42. data/lib/karafka/instrumentation/vendors/appsignal/base.rb +1 -1
  43. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +4 -0
  44. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +32 -16
  45. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +2 -2
  46. data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +1 -1
  47. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +3 -15
  48. data/lib/karafka/licenser.rb +1 -1
  49. data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
  50. data/lib/karafka/messages/messages.rb +32 -0
  51. data/lib/karafka/pro/active_job/consumer.rb +2 -2
  52. data/lib/karafka/pro/active_job/dispatcher.rb +3 -3
  53. data/lib/karafka/pro/cleaner/messages/messages.rb +1 -1
  54. data/lib/karafka/pro/cleaner.rb +3 -3
  55. data/lib/karafka/pro/cli/contracts/server.rb +1 -1
  56. data/lib/karafka/pro/cli/parallel_segments/base.rb +4 -3
  57. data/lib/karafka/pro/cli/parallel_segments/collapse.rb +1 -1
  58. data/lib/karafka/pro/cli/parallel_segments/distribute.rb +1 -1
  59. data/lib/karafka/pro/cli/parallel_segments.rb +1 -1
  60. data/lib/karafka/pro/connection/manager.rb +1 -2
  61. data/lib/karafka/pro/connection/multiplexing/listener.rb +1 -0
  62. data/lib/karafka/pro/contracts/base.rb +1 -1
  63. data/lib/karafka/pro/encryption/cipher.rb +3 -2
  64. data/lib/karafka/pro/encryption/contracts/config.rb +1 -1
  65. data/lib/karafka/pro/encryption/messages/parser.rb +1 -1
  66. data/lib/karafka/pro/encryption/setup/config.rb +1 -1
  67. data/lib/karafka/pro/iterator/tpl_builder.rb +1 -1
  68. data/lib/karafka/pro/iterator.rb +1 -1
  69. data/lib/karafka/pro/loader.rb +1 -1
  70. data/lib/karafka/pro/processing/coordinator.rb +1 -1
  71. data/lib/karafka/pro/processing/filters/base.rb +1 -0
  72. data/lib/karafka/pro/processing/filters/delayer.rb +1 -1
  73. data/lib/karafka/pro/processing/filters/expirer.rb +1 -1
  74. data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +1 -1
  75. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +1 -1
  76. data/lib/karafka/pro/processing/jobs/eofed_non_blocking.rb +1 -1
  77. data/lib/karafka/pro/processing/jobs/periodic.rb +1 -1
  78. data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +1 -1
  79. data/lib/karafka/pro/processing/jobs_builder.rb +1 -1
  80. data/lib/karafka/pro/processing/jobs_queue.rb +0 -2
  81. data/lib/karafka/pro/processing/offset_metadata/fetcher.rb +1 -0
  82. data/lib/karafka/pro/processing/partitioner.rb +1 -1
  83. data/lib/karafka/pro/processing/strategies/base.rb +1 -1
  84. data/lib/karafka/pro/processing/strategies/default.rb +2 -2
  85. data/lib/karafka/pro/processing/strategies/dlq/default.rb +1 -1
  86. data/lib/karafka/pro/processing/strategies/vp/default.rb +1 -1
  87. data/lib/karafka/pro/processing/strategy_selector.rb +1 -0
  88. data/lib/karafka/pro/processing/virtual_partitions/distributors/balanced.rb +4 -2
  89. data/lib/karafka/pro/processing/virtual_partitions/distributors/consistent.rb +4 -2
  90. data/lib/karafka/pro/recurring_tasks/consumer.rb +3 -2
  91. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +2 -2
  92. data/lib/karafka/pro/recurring_tasks/contracts/task.rb +1 -1
  93. data/lib/karafka/pro/recurring_tasks/deserializer.rb +1 -1
  94. data/lib/karafka/pro/recurring_tasks/dispatcher.rb +1 -1
  95. data/lib/karafka/pro/recurring_tasks/executor.rb +2 -1
  96. data/lib/karafka/pro/recurring_tasks/schedule.rb +5 -2
  97. data/lib/karafka/pro/recurring_tasks/serializer.rb +6 -5
  98. data/lib/karafka/pro/recurring_tasks/setup/config.rb +2 -2
  99. data/lib/karafka/pro/recurring_tasks/task.rb +1 -1
  100. data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +3 -0
  101. data/lib/karafka/pro/routing/features/multiplexing/subscription_groups_builder.rb +1 -1
  102. data/lib/karafka/pro/routing/features/multiplexing.rb +5 -5
  103. data/lib/karafka/pro/routing/features/offset_metadata.rb +4 -4
  104. data/lib/karafka/pro/routing/features/parallel_segments/builder.rb +1 -1
  105. data/lib/karafka/pro/routing/features/patterns/patterns.rb +1 -1
  106. data/lib/karafka/pro/routing/features/periodic_job/topic.rb +1 -1
  107. data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +1 -1
  108. data/lib/karafka/pro/routing/features/swarm.rb +1 -1
  109. data/lib/karafka/pro/routing/features/throttling/topic.rb +3 -1
  110. data/lib/karafka/pro/scheduled_messages/consumer.rb +1 -1
  111. data/lib/karafka/pro/scheduled_messages/contracts/config.rb +2 -2
  112. data/lib/karafka/pro/scheduled_messages/contracts/message.rb +1 -1
  113. data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +3 -2
  114. data/lib/karafka/pro/scheduled_messages/day.rb +1 -0
  115. data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +1 -1
  116. data/lib/karafka/pro/scheduled_messages/deserializers/payload.rb +1 -1
  117. data/lib/karafka/pro/scheduled_messages/max_epoch.rb +1 -0
  118. data/lib/karafka/pro/scheduled_messages/proxy.rb +1 -1
  119. data/lib/karafka/pro/scheduled_messages/serializer.rb +3 -3
  120. data/lib/karafka/pro/scheduled_messages/setup/config.rb +2 -2
  121. data/lib/karafka/pro/scheduled_messages/state.rb +1 -0
  122. data/lib/karafka/pro/scheduled_messages/tracker.rb +1 -0
  123. data/lib/karafka/process.rb +4 -4
  124. data/lib/karafka/processing/executor.rb +1 -1
  125. data/lib/karafka/processing/inline_insights/tracker.rb +1 -0
  126. data/lib/karafka/processing/jobs_queue.rb +1 -1
  127. data/lib/karafka/processing/result.rb +1 -0
  128. data/lib/karafka/processing/strategies/dlq.rb +1 -1
  129. data/lib/karafka/processing/strategy_selector.rb +1 -0
  130. data/lib/karafka/routing/activity_manager.rb +1 -0
  131. data/lib/karafka/routing/builder.rb +3 -1
  132. data/lib/karafka/routing/consumer_group.rb +19 -1
  133. data/lib/karafka/routing/contracts/consumer_group.rb +3 -2
  134. data/lib/karafka/routing/contracts/topic.rb +5 -2
  135. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -1
  136. data/lib/karafka/routing/features/declaratives/topic.rb +5 -2
  137. data/lib/karafka/routing/features/deserializers/topic.rb +3 -3
  138. data/lib/karafka/routing/features/inline_insights.rb +5 -5
  139. data/lib/karafka/routing/router.rb +1 -1
  140. data/lib/karafka/routing/subscription_group.rb +2 -2
  141. data/lib/karafka/routing/subscription_groups_builder.rb +18 -2
  142. data/lib/karafka/routing/topic.rb +3 -3
  143. data/lib/karafka/server.rb +1 -1
  144. data/lib/karafka/setup/attributes_map.rb +4 -2
  145. data/lib/karafka/setup/config.rb +21 -10
  146. data/lib/karafka/setup/config_proxy.rb +209 -0
  147. data/lib/karafka/setup/contracts/config.rb +1 -1
  148. data/lib/karafka/swarm/liveness_listener.rb +1 -0
  149. data/lib/karafka/swarm/manager.rb +7 -6
  150. data/lib/karafka/swarm/node.rb +1 -1
  151. data/lib/karafka/swarm/supervisor.rb +1 -0
  152. data/lib/karafka/time_trackers/base.rb +1 -1
  153. data/lib/karafka/version.rb +1 -1
  154. data/lib/karafka.rb +2 -3
  155. metadata +8 -65
  156. data/.coditsu/ci.yml +0 -3
  157. data/.console_irbrc +0 -11
  158. data/.github/CODEOWNERS +0 -3
  159. data/.github/FUNDING.yml +0 -1
  160. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
  161. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  162. data/.github/workflows/ci_linux_ubuntu_x86_64_gnu.yml +0 -278
  163. data/.github/workflows/ci_macos_arm64.yml +0 -151
  164. data/.github/workflows/push.yml +0 -35
  165. data/.github/workflows/trigger-wiki-refresh.yml +0 -30
  166. data/.github/workflows/verify-action-pins.yml +0 -16
  167. data/.gitignore +0 -69
  168. data/.rspec +0 -7
  169. data/.ruby-gemset +0 -1
  170. data/.ruby-version +0 -1
  171. data/CODE_OF_CONDUCT.md +0 -46
  172. data/CONTRIBUTING.md +0 -32
  173. data/Gemfile +0 -28
  174. data/Gemfile.lock +0 -173
  175. data/Rakefile +0 -4
  176. data/SECURITY.md +0 -23
  177. data/bin/benchmarks +0 -99
  178. data/bin/clean_kafka +0 -43
  179. data/bin/create_token +0 -22
  180. data/bin/integrations +0 -341
  181. data/bin/record_rss +0 -50
  182. data/bin/rspecs +0 -26
  183. data/bin/scenario +0 -29
  184. data/bin/stress_many +0 -13
  185. data/bin/stress_one +0 -13
  186. data/bin/verify_kafka_warnings +0 -36
  187. data/bin/verify_license_integrity +0 -37
  188. data/bin/verify_topics_naming +0 -27
  189. data/bin/wait_for_kafka +0 -24
  190. data/docker-compose.yml +0 -25
  191. data/examples/payloads/avro/.gitkeep +0 -0
  192. data/examples/payloads/json/sample_set_01/enrollment_event.json +0 -579
  193. data/examples/payloads/json/sample_set_01/ingestion_event.json +0 -30
  194. data/examples/payloads/json/sample_set_01/transaction_event.json +0 -17
  195. data/examples/payloads/json/sample_set_01/user_event.json +0 -11
  196. data/examples/payloads/json/sample_set_02/download.json +0 -191
  197. data/examples/payloads/json/sample_set_03/event_type_1.json +0 -18
  198. data/examples/payloads/json/sample_set_03/event_type_2.json +0 -263
  199. data/examples/payloads/json/sample_set_03/event_type_3.json +0 -41
  200. data/log/.gitkeep +0 -0
  201. data/renovate.json +0 -21
@@ -24,22 +24,38 @@ module Karafka
24
24
 
25
25
  private_constant :DISTRIBUTION_KEYS
26
26
 
27
+ # Initializes the subscription groups builder
27
28
  def initialize
28
29
  @position = -1
29
30
  end
30
31
 
31
32
  # @param topics [Karafka::Routing::Topics] all the topics based on which we want to build
32
33
  # subscription groups
34
+ # @param base_position [Integer, nil] optional starting position for subscription groups.
35
+ # When provided, positions will start from this value instead of continuing from the
36
+ # global counter. This is used when rebuilding subscription groups to maintain stable
37
+ # positions for static group membership.
33
38
  # @return [Array<SubscriptionGroup>] all subscription groups we need in separate threads
34
- def call(topics)
39
+ def call(topics, base_position: nil)
40
+ # If base_position is provided, use it for stable rebuilding (consumer group reopening).
41
+ # Otherwise continue from global counter for new subscription groups.
42
+ # We subtract 1 from base_position because position is incremented in the map block below.
43
+ # This ensures the first subscription group gets the correct base_position value.
44
+ use_base = !base_position.nil?
45
+ position = use_base ? base_position - 1 : @position
46
+
35
47
  topics
36
48
  .map { |topic| [checksum(topic), topic] }
37
49
  .group_by(&:first)
38
50
  .values
39
51
  .map { |value| value.map(&:last) }
40
52
  .flat_map { |value| expand(value) }
41
- .map { |grouped_topics| SubscriptionGroup.new(@position += 1, grouped_topics) }
53
+ .map { |grouped_topics| SubscriptionGroup.new(position += 1, grouped_topics) }
42
54
  .tap do |subscription_groups|
55
+ # Always ensure global counter is at least as high as the highest position used.
56
+ # This prevents position collisions when new consumer groups are created after
57
+ # existing ones are rebuilt with base_position.
58
+ @position = position if position > @position
43
59
  subscription_groups.each do |subscription_group|
44
60
  subscription_group.topics.each do |topic|
45
61
  topic.subscription_group = subscription_group
@@ -29,7 +29,7 @@ module Karafka
29
29
 
30
30
  private_constant :INHERITABLE_ATTRIBUTES
31
31
 
32
- # @param [String, Symbol] name of a topic on which we want to listen
32
+ # @param name [String, Symbol] name of a topic on which we want to listen
33
33
  # @param consumer_group [Karafka::Routing::ConsumerGroup] owning consumer group of this topic
34
34
  def initialize(name, consumer_group)
35
35
  @name = name.to_s
@@ -92,7 +92,7 @@ module Karafka
92
92
  # consumer class is defined with a name. It won't support code reload for anonymous
93
93
  # consumer classes, but this is an edge case
94
94
  begin
95
- ::Object.const_get(@consumer.to_s)
95
+ Object.const_get(@consumer.to_s)
96
96
  rescue NameError
97
97
  # It will only fail if the in case of anonymous classes
98
98
  @consumer
@@ -139,7 +139,7 @@ module Karafka
139
139
  [attribute, public_send(attribute)]
140
140
  end
141
141
 
142
- Hash[map].merge!(
142
+ map.to_h.merge!(
143
143
  id: id,
144
144
  name: name,
145
145
  active: active?,
@@ -174,7 +174,7 @@ module Karafka
174
174
  # This ensures that if users have configured the default pool, it is closed correctly
175
175
  #
176
176
  # Custom pools need to be closed by users themselves
177
- ::WaterDrop::ConnectionPool.close
177
+ WaterDrop::ConnectionPool.close
178
178
 
179
179
  Karafka::App.terminate!
180
180
  end
@@ -122,6 +122,7 @@ module Karafka
122
122
  sasl.oauthbearer.config
123
123
  sasl.oauthbearer.extensions
124
124
  sasl.oauthbearer.grant.type
125
+ sasl.oauthbearer.metadata.authentication.type
125
126
  sasl.oauthbearer.method
126
127
  sasl.oauthbearer.scope
127
128
  sasl.oauthbearer.token.endpoint.url
@@ -279,6 +280,7 @@ module Karafka
279
280
  sasl.oauthbearer.config
280
281
  sasl.oauthbearer.extensions
281
282
  sasl.oauthbearer.grant.type
283
+ sasl.oauthbearer.metadata.authentication.type
282
284
  sasl.oauthbearer.method
283
285
  sasl.oauthbearer.scope
284
286
  sasl.oauthbearer.token.endpoint.url
@@ -357,7 +359,7 @@ module Karafka
357
359
  end
358
360
 
359
361
  # @private
360
- # @return [Hash<Symbol, Array<Symbol>>] hash with consumer and producer attributes list
362
+ # @return [Hash{Symbol => Array<Symbol>}] hash with consumer and producer attributes list
361
363
  # that is sorted.
362
364
  # @note This method should not be used directly. It is only used to generate appropriate
363
365
  # options list in case it would change
@@ -367,7 +369,7 @@ module Karafka
367
369
 
368
370
  attributes = { consumer: Set.new, producer: Set.new }
369
371
 
370
- ::URI.parse(SOURCE).open.readlines.each do |line|
372
+ URI.parse(SOURCE).open.readlines.each do |line|
371
373
  next unless line.include?('|')
372
374
 
373
375
  attribute, attribute_type = line.split('|').map(&:strip)
@@ -12,7 +12,7 @@ module Karafka
12
12
  # enough and will still keep the code simple
13
13
  # @see Karafka::Setup::Configurators::Base for more details about configurators api
14
14
  class Config
15
- extend ::Karafka::Core::Configurable
15
+ extend Karafka::Core::Configurable
16
16
 
17
17
  # Available settings
18
18
 
@@ -33,9 +33,9 @@ module Karafka
33
33
  # Used only for logging.
34
34
  setting :client_id, default: 'karafka'
35
35
  # option logger [Instance] logger that we want to use
36
- setting :logger, default: ::Karafka::Instrumentation::Logger.new
36
+ setting :logger, default: Karafka::Instrumentation::Logger.new
37
37
  # option monitor [Instance] monitor that we will to use (defaults to Karafka::Monitor)
38
- setting :monitor, default: ::Karafka::Instrumentation::Monitor.new
38
+ setting :monitor, default: Karafka::Instrumentation::Monitor.new
39
39
  # option [Boolean] should we reload consumers with each incoming batch thus effectively
40
40
  # supporting code reload (if someone reloads code) or should we keep the persistence
41
41
  setting :consumer_persistence, default: true
@@ -211,7 +211,7 @@ module Karafka
211
211
  setting :cli do
212
212
  # option contract [Object] cli setup validation contract (in the context of options and
213
213
  # topics)
214
- setting :contract, default: ::Karafka::Cli::Contracts::Server.new
214
+ setting :contract, default: Karafka::Cli::Contracts::Server.new
215
215
  end
216
216
 
217
217
  setting :routing do
@@ -345,7 +345,7 @@ module Karafka
345
345
  # (incoming). Can be replaced with a custom implementation for formats like Avro,
346
346
  # Protobuf, etc. This is a global setting because Rails serializes jobs before
347
347
  # Karafka receives them, so we need a consistent approach across all ActiveJob topics.
348
- setting :deserializer, default: ::Karafka::ActiveJob::Deserializer.new
348
+ setting :deserializer, default: Karafka::ActiveJob::Deserializer.new
349
349
  end
350
350
  end
351
351
 
@@ -415,17 +415,21 @@ module Karafka
415
415
  # of the pro defaults with custom components
416
416
  Pro::Loader.pre_setup_all(config) if Karafka.pro?
417
417
 
418
- configure(&)
418
+ # Wrap config in a proxy that intercepts producer block configuration
419
+ proxy = ConfigProxy.new(config)
420
+ # We need to check for the block presence here because user can just run setup without
421
+ # any block given
422
+ configure { yield(proxy) if block_given? }
419
423
 
420
424
  Contracts::Config.new.validate!(
421
425
  config.to_h,
422
426
  scope: %w[config]
423
427
  )
424
428
 
425
- configure_components
429
+ configure_components(proxy)
426
430
 
427
431
  # Refreshes the references that are cached that might have been changed by the config
428
- ::Karafka.refresh!
432
+ Karafka.refresh!
429
433
 
430
434
  # Post-setup configure all routing features that would need this
431
435
  Routing::Features::Base.post_setup_all(config)
@@ -443,14 +447,16 @@ module Karafka
443
447
  private
444
448
 
445
449
  # Sets up all the components that are based on the user configuration
450
+ # @param config_proxy [ConfigProxy] the configuration proxy containing deferred setup
451
+ # blocks
446
452
  # @note At the moment it is only WaterDrop
447
- def configure_components
453
+ def configure_components(config_proxy)
448
454
  oauth_listener = config.oauth.token_provider_listener
449
455
  # We need to subscribe the oauth listener here because we want it to be ready before
450
456
  # any consumer/admin runs
451
457
  Karafka::App.monitor.subscribe(oauth_listener) if oauth_listener
452
458
 
453
- config.producer ||= ::WaterDrop::Producer.new do |producer_config|
459
+ config.producer ||= WaterDrop::Producer.new do |producer_config|
454
460
  # In some cases WaterDrop updates the config and we don't want our consumer config to
455
461
  # be polluted by those updates, that's why we copy
456
462
  producer_kafka = AttributesMap.producer(config.kafka.dup)
@@ -463,6 +469,11 @@ module Karafka
463
469
  producer_config.oauth.token_provider_listener = oauth_listener
464
470
  producer_config.logger = config.logger
465
471
  end
472
+
473
+ # Execute user's producer configuration block
474
+ # This happens after the default producer setup, allowing users to customize settings
475
+ # If no block was provided during setup, this will be an empty lambda that does nothing
476
+ config_proxy.producer_initialization_block.call(config.producer.config)
466
477
  end
467
478
  end
468
479
  end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Setup
5
+ # Configuration proxy that wraps the actual config object during the setup phase.
6
+ #
7
+ # ## Purpose
8
+ #
9
+ # This proxy exists to intercept specific configuration methods during the setup block
10
+ # execution, allowing for deferred initialization and special handling of certain
11
+ # configuration aspects without permanently modifying the config object's API.
12
+ #
13
+ # The key design principle is: **the proxy only exists during setup and doesn't pollute
14
+ # the permanent config API**. Once setup is complete, all access goes directly to the
15
+ # real config object.
16
+ #
17
+ # ## Why Use a Proxy?
18
+ #
19
+ # During Karafka setup, there's a specific order of operations:
20
+ #
21
+ # 1. User configures basic settings (kafka, client_id, etc.)
22
+ # 2. Config validation runs
23
+ # 3. Components are initialized based on finalized config
24
+ # 4. Post-setup hooks execute
25
+ #
26
+ # Some configuration needs to happen **after** user settings but **during** component
27
+ # initialization. The proxy intercepts these special cases during step 1, stores the
28
+ # instructions, and applies them during step 3.
29
+ #
30
+ # ## Current Use Case: Producer Configuration
31
+ #
32
+ # The proxy currently intercepts `producer` calls with blocks:
33
+ #
34
+ # ```ruby
35
+ # Karafka::App.setup do |config|
36
+ # config.kafka = { 'bootstrap.servers': 'localhost:9092' }
37
+ #
38
+ # # This is intercepted by the proxy
39
+ # config.producer do |producer_config|
40
+ # producer_config.kafka['compression.type'] = 'snappy'
41
+ # end
42
+ # end
43
+ # ```
44
+ #
45
+ # Without the proxy, we'd have two problems:
46
+ #
47
+ # 1. **Permanent API pollution**: Adding a `producer` method to config that accepts blocks
48
+ # would change its permanent API, even though this functionality is only needed during
49
+ # setup.
50
+ #
51
+ # 2. **Timing issues**: The producer doesn't exist yet when the user's setup block runs.
52
+ # The producer is created in `configure_components` after all user configuration is
53
+ # complete. We need to store the user's producer configuration block and apply it
54
+ # later at the right time.
55
+ #
56
+ # The proxy solves both:
57
+ # - It only exists during the setup call
58
+ # - It stores the producer configuration block in an instance variable
59
+ # - After setup, the block is passed to `configure_components` for execution
60
+ # - The proxy is then discarded
61
+ #
62
+ # ## Future Use Cases
63
+ #
64
+ # This pattern can be extended for other deferred configuration needs:
65
+ #
66
+ # - **Monitor configuration**: Intercept monitor setup to configure it after all components
67
+ # are initialized
68
+ # - **Custom component initialization**: Allow users to configure internal components that
69
+ # need access to the fully-configured environment
70
+ # - **Feature toggles**: Enable/disable features based on the complete configuration state
71
+ #
72
+ # ## Implementation Details
73
+ #
74
+ # The proxy uses Ruby's SimpleDelegator pattern:
75
+ # 1. Inherits from SimpleDelegator for automatic delegation
76
+ # 2. Implements specific interceptor methods (currently just `producer`)
77
+ # 3. Delegates everything else to the wrapped config automatically
78
+ # 4. Stores deferred configuration in instance variables
79
+ #
80
+ # ## Lifecycle
81
+ #
82
+ # ```
83
+ # Setup.setup(&block)
84
+ # ↓
85
+ # proxy = ConfigProxy.new(config) # Create proxy
86
+ # ↓
87
+ # configure { yield(proxy) } # User block receives proxy
88
+ # ↓
89
+ # [User calls config methods] # Most delegate to real config
90
+ # ↓
91
+ # [User calls config.producer {}] # Intercepted by proxy
92
+ # ↓
93
+ # configure_components(proxy.producer_initialization_block) # Block retrieved
94
+ # ↓
95
+ # [Block executed with real producer config]
96
+ # ↓
97
+ # proxy = nil # Proxy discarded
98
+ # ```
99
+ #
100
+ # ## Example Usage
101
+ #
102
+ # ```ruby
103
+ # class KarafkaApp < Karafka::App
104
+ # setup do |config|
105
+ # # Standard config access - delegated to real config
106
+ # config.kafka = { 'bootstrap.servers': 'localhost:9092' }
107
+ # config.client_id = 'my_app'
108
+ #
109
+ # # Special intercepted method - handled by proxy
110
+ # config.producer do |producer_config|
111
+ # producer_config.kafka['compression.type'] = 'snappy'
112
+ # producer_config.kafka['linger.ms'] = 10
113
+ # producer_config.max_wait_timeout = 60_000
114
+ # end
115
+ # end
116
+ # end
117
+ # ```
118
+ #
119
+ # @see Karafka::Setup::Config.setup
120
+ # @see Karafka::Setup::Config.configure_components
121
+ class ConfigProxy < SimpleDelegator
122
+ # @return [Proc] the stored producer initialization block (defaults to empty lambda)
123
+ attr_reader :producer_initialization_block
124
+
125
+ # Creates a new configuration proxy wrapping the actual config object.
126
+ #
127
+ # Uses SimpleDelegator to automatically delegate all method calls to the wrapped config
128
+ # except for specifically intercepted methods like {#producer}.
129
+ #
130
+ # The producer initialization block defaults to an empty lambda, eliminating the need for
131
+ # nil checks when executing the block in {#configure_components}.
132
+ #
133
+ # @param config [Karafka::Setup::Config::Node] the actual config object to wrap
134
+ #
135
+ # @example
136
+ # proxy = ConfigProxy.new(Karafka::App.config)
137
+ def initialize(config)
138
+ super
139
+ @producer_initialization_block = ->(_) {}
140
+ end
141
+
142
+ # Captures a block for producer configuration or delegates producer assignment to config.
143
+ #
144
+ # This method has dual behavior:
145
+ #
146
+ # 1. **With a block**: Stores the block for later execution after the producer is created.
147
+ # This allows users to customize producer settings without manually creating a producer
148
+ # instance.
149
+ #
150
+ # 2. **With an instance**: Delegates to `config.producer=` for direct producer assignment.
151
+ # This preserves the existing API for users who want to provide their own producer.
152
+ #
153
+ # The block is stored in `@producer_initialization_block` and later passed to
154
+ # `configure_components` where it's executed with the producer's config object.
155
+ #
156
+ # ## Why This Exists
157
+ #
158
+ # The producer is created in `configure_components` after all user configuration is
159
+ # complete. This ensures the producer inherits the correct kafka settings. However,
160
+ # users may want to customize the producer further (add middleware, change timeouts,
161
+ # etc.) without creating their own producer instance.
162
+ #
163
+ # This method bridges the gap: it lets users configure the producer **as if it exists**
164
+ # during setup, but actually defers the configuration until after it's created.
165
+ #
166
+ # ## Block Execution Timing
167
+ #
168
+ # ```
169
+ # setup do |config|
170
+ # config.kafka = { ... } # Runs immediately
171
+ #
172
+ # config.producer do |pc| # Block STORED (not executed yet)
173
+ # pc.kafka['...'] = '...'
174
+ # end
175
+ # end
176
+ # # User block complete
177
+ # # Config validation runs
178
+ # # configure_components creates producer
179
+ # # NOW the stored block executes: block.call(producer.config)
180
+ # ```
181
+ #
182
+ # @param instance [WaterDrop::Producer, nil] optional producer instance for direct
183
+ # assignment
184
+ # @param block [Proc] optional block for producer configuration. Will be called with
185
+ # the producer's config object after the producer is created.
186
+ # @return [void]
187
+ #
188
+ # @example Configuring producer with a block
189
+ # config.producer do |producer_config|
190
+ # producer_config.kafka['compression.type'] = 'snappy'
191
+ # producer_config.max_wait_timeout = 30_000
192
+ # producer_config.middleware.append(MyMiddleware.new)
193
+ # end
194
+ #
195
+ # @example Direct producer assignment
196
+ # custom_producer = WaterDrop::Producer.new { |c| c.kafka = { 'bootstrap.servers' => 'localhost:9092' } }
197
+ # config.producer = custom_producer
198
+ def producer(instance = nil, &block)
199
+ if block
200
+ # Store the configuration block for later execution
201
+ @producer_initialization_block = block
202
+ else
203
+ # Direct assignment - delegate to real config via __getobj__
204
+ __getobj__.producer = instance
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -18,7 +18,7 @@ module Karafka
18
18
  end
19
19
 
20
20
  # Topics regexp constant reference for easier usage
21
- TOPIC_REGEXP = ::Karafka::Contracts::TOPIC_REGEXP
21
+ TOPIC_REGEXP = Karafka::Contracts::TOPIC_REGEXP
22
22
 
23
23
  private_constant :TOPIC_REGEXP
24
24
 
@@ -13,6 +13,7 @@ module Karafka
13
13
  orphaned_exit_code: %i[internal swarm orphaned_exit_code]
14
14
  )
15
15
 
16
+ # Initializes the liveness listener
16
17
  def initialize
17
18
  @last_checked_at = 0
18
19
  @mutex = Mutex.new
@@ -29,6 +29,7 @@ module Karafka
29
29
  # @return [Array<Node>] All nodes that manager manages
30
30
  attr_reader :nodes
31
31
 
32
+ # Initializes the swarm manager with empty nodes
32
33
  def initialize
33
34
  @nodes = []
34
35
  @statuses = Hash.new { |h, k| h[k] = {} }
@@ -97,7 +98,7 @@ module Karafka
97
98
  # If we've issued a stop to this process and it does not want to stop in the period, kills it
98
99
  #
99
100
  # @param statuses [Hash] hash with statuses transitions with times
100
- # @param [Swarm::Node] node we're checking
101
+ # @param node [Swarm::Node] node we're checking
101
102
  # @return [Boolean] should it be the last action taken on this node in this run
102
103
  def terminate_if_hanging(statuses, node)
103
104
  return false unless statuses.key?(:stop)
@@ -118,7 +119,7 @@ module Karafka
118
119
  # reported it is not healthy.
119
120
  #
120
121
  # @param statuses [Hash] hash with statuses transitions with times
121
- # @param [Swarm::Node] node we're checking
122
+ # @param node [Swarm::Node] node we're checking
122
123
  # @return [Boolean] should it be the last action taken on this node in this run
123
124
  def stop_if_not_healthy(statuses, node)
124
125
  status = node.status
@@ -146,7 +147,7 @@ module Karafka
146
147
  # If node stopped responding, starts the stopping procedure.
147
148
  #
148
149
  # @param statuses [Hash] hash with statuses transitions with times
149
- # @param [Swarm::Node] node we're checking
150
+ # @param node [Swarm::Node] node we're checking
150
151
  # @return [Boolean] should it be the last action taken on this node in this run
151
152
  def stop_if_not_responding(statuses, node)
152
153
  # Do nothing if already stopping
@@ -171,7 +172,7 @@ module Karafka
171
172
  # Cleans up a dead process and remembers time of death for restart after a period.
172
173
  #
173
174
  # @param statuses [Hash] hash with statuses transitions with times
174
- # @param [Swarm::Node] node we're checking
175
+ # @param node [Swarm::Node] node we're checking
175
176
  # @return [Boolean] should it be the last action taken on this node in this run
176
177
  def cleanup_one(statuses, node)
177
178
  return false if statuses.key?(:dead_since)
@@ -188,7 +189,7 @@ module Karafka
188
189
  # killed for some external reason.
189
190
  #
190
191
  # @param statuses [Hash] hash with statuses transitions with times
191
- # @param [Swarm::Node] node we're checking
192
+ # @param node [Swarm::Node] node we're checking
192
193
  # @return [Boolean] should it be the last action taken on this node in this run
193
194
  def restart_after_timeout(statuses, node)
194
195
  return false unless over?(statuses[:dead_since], node_restart_timeout)
@@ -200,7 +201,7 @@ module Karafka
200
201
 
201
202
  # Starts a new node (or restarts dead)
202
203
  #
203
- # @param [Swarm::Node] node we're starting
204
+ # @param node [Swarm::Node] node we're starting
204
205
  def start_one(node)
205
206
  instr_args = { caller: self, node: node }
206
207
 
@@ -73,7 +73,7 @@ module Karafka
73
73
  old_producer_config = old_producer.config
74
74
 
75
75
  # Supervisor producer is closed, hence we need a new one here
76
- config.producer = ::WaterDrop::Producer.new do |p_config|
76
+ config.producer = WaterDrop::Producer.new do |p_config|
77
77
  p_config.kafka = Setup::AttributesMap.producer(kafka.dup)
78
78
  p_config.logger = config.logger
79
79
 
@@ -34,6 +34,7 @@ module Karafka
34
34
 
35
35
  private_constant :SHUTDOWN_GRACE_PERIOD
36
36
 
37
+ # Initializes the swarm supervisor
37
38
  def initialize
38
39
  @mutex = Mutex.new
39
40
  @queue = Queue.new
@@ -8,7 +8,7 @@ module Karafka
8
8
  module TimeTrackers
9
9
  # Base class for all the time-trackers.
10
10
  class Base
11
- include ::Karafka::Core::Helpers::Time
11
+ include Karafka::Core::Helpers::Time
12
12
  end
13
13
  end
14
14
  end
@@ -3,5 +3,5 @@
3
3
  # Main module namespace
4
4
  module Karafka
5
5
  # Current Karafka version
6
- VERSION = '2.5.2'
6
+ VERSION = '2.5.4.rc1'
7
7
  end
data/lib/karafka.rb CHANGED
@@ -11,7 +11,6 @@ require 'fileutils'
11
11
  require 'openssl'
12
12
  require 'optparse'
13
13
  require 'socket'
14
- require 'base64'
15
14
  require 'date'
16
15
  require 'singleton'
17
16
  require 'digest'
@@ -34,7 +33,7 @@ module Karafka
34
33
  env.replace(environment.to_s)
35
34
  end
36
35
 
37
- # @return [Logger] logger that we want to use. Will use ::Karafka::Logger by default
36
+ # @return [Logger] logger that we want to use. Will use Karafka::Logger by default
38
37
  def logger
39
38
  @logger ||= App.config.logger
40
39
  end
@@ -148,7 +147,7 @@ module Karafka
148
147
  #
149
148
  # This method refreshes the things that might have been altered by the configuration
150
149
  def refresh!
151
- config = ::Karafka::App.config
150
+ config = Karafka::App.config
152
151
 
153
152
  @logger = config.logger
154
153
  @producer = config.producer