karafka 2.3.0 → 2.3.2

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 (75) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +15 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +22 -22
  7. data/README.md +2 -2
  8. data/bin/integrations +2 -1
  9. data/bin/rspecs +6 -2
  10. data/config/locales/errors.yml +30 -8
  11. data/config/locales/pro_errors.yml +2 -0
  12. data/docker-compose.yml +1 -1
  13. data/lib/karafka/app.rb +14 -0
  14. data/lib/karafka/cli/base.rb +19 -0
  15. data/lib/karafka/cli/server.rb +62 -76
  16. data/lib/karafka/cli/swarm.rb +30 -0
  17. data/lib/karafka/constraints.rb +3 -3
  18. data/lib/karafka/contracts/config.rb +19 -0
  19. data/lib/karafka/errors.rb +12 -0
  20. data/lib/karafka/helpers/async.rb +13 -3
  21. data/lib/karafka/helpers/config_importer.rb +30 -0
  22. data/lib/karafka/instrumentation/logger_listener.rb +31 -0
  23. data/lib/karafka/instrumentation/notifications.rb +9 -0
  24. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +2 -0
  25. data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +72 -0
  26. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +11 -40
  27. data/lib/karafka/instrumentation/vendors/kubernetes/swarm_liveness_listener.rb +54 -0
  28. data/lib/karafka/pro/active_job/job_options_contract.rb +1 -1
  29. data/lib/karafka/pro/base_consumer.rb +16 -0
  30. data/lib/karafka/pro/connection/manager.rb +6 -1
  31. data/lib/karafka/pro/processing/coordinator.rb +13 -3
  32. data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +74 -0
  33. data/lib/karafka/pro/processing/coordinators/filters_applier.rb +107 -0
  34. data/lib/karafka/pro/processing/coordinators/virtual_offset_manager.rb +180 -0
  35. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +5 -7
  36. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +5 -7
  37. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +8 -10
  38. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +8 -16
  39. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +5 -7
  40. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +5 -7
  41. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +8 -10
  42. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +7 -9
  43. data/lib/karafka/pro/processing/strategies/dlq/default.rb +36 -10
  44. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +3 -7
  45. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +4 -8
  46. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +6 -9
  47. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +5 -15
  48. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +4 -8
  49. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +6 -9
  50. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +10 -20
  51. data/lib/karafka/pro/processing/strategies/vp/default.rb +7 -0
  52. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +6 -0
  53. data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +39 -0
  54. data/lib/karafka/pro/swarm/liveness_listener.rb +171 -0
  55. data/lib/karafka/process.rb +27 -1
  56. data/lib/karafka/routing/features/dead_letter_queue/config.rb +2 -0
  57. data/lib/karafka/routing/subscription_group.rb +31 -9
  58. data/lib/karafka/runner.rb +4 -0
  59. data/lib/karafka/server.rb +13 -16
  60. data/lib/karafka/setup/config.rb +41 -2
  61. data/lib/karafka/status.rb +4 -2
  62. data/lib/karafka/swarm/liveness_listener.rb +55 -0
  63. data/lib/karafka/swarm/manager.rb +217 -0
  64. data/lib/karafka/swarm/node.rb +179 -0
  65. data/lib/karafka/swarm/pidfd.rb +131 -0
  66. data/lib/karafka/swarm/supervisor.rb +184 -0
  67. data/lib/karafka/swarm.rb +27 -0
  68. data/lib/karafka/templates/karafka.rb.erb +0 -2
  69. data/lib/karafka/version.rb +1 -1
  70. data/lib/karafka.rb +1 -1
  71. data.tar.gz.sig +0 -0
  72. metadata +17 -4
  73. metadata.gz.sig +0 -0
  74. data/lib/karafka/pro/processing/filters_applier.rb +0 -105
  75. data/lib/karafka/pro/processing/virtual_offset_manager.rb +0 -177
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bceb8995575bd7c51d4fa6df9905619082080c932b5295749d9562fb53eba2a7
4
- data.tar.gz: d16247c466fe08f6a671e48e8b25af7edcf2c32d769a497a46245773a66794de
3
+ metadata.gz: cb47082224d857f3029f9bb8e1b04a35e6b8ed2f7ae75bbe52bf1b778ff56226
4
+ data.tar.gz: 53d59fd7e140f5b3e9b89dd3e4af28469bc534074110e2e93fae24c59bf81b88
5
5
  SHA512:
6
- metadata.gz: 3367c6bace7ab7a1ed7c6ef04e41eefa40c6fef7baf41d0e8e32bb891e1f2799e1564a6ee36b38735b5d514272c88c62e90bcbb54ffa45f175e80b888837d1e5
7
- data.tar.gz: 2d31b87c97ce9deb3d47c0332782ad116dbcf4661a7ea55b35ac04a0e87003a8eb73a5b50f7fc9a21eb1ad288eaafff0528b95133fe58f97ada832526959b628
6
+ metadata.gz: 00e09a345122ad2facaf8adcbb52fae3ce87374083d9e6785a1f07a74c87e53c6b0b3dd32b82566e846d160569aafc55c614013d8c9f95664612150fb51d07b1
7
+ data.tar.gz: 283e50a6b3b25579419bdc9b947e5ba802e22a1cd6d0097ab8929c5394d3858461bda15c3e70a0d2d466a8705466ef1d5b24e1a8bfe5ac8e356c64790049c7b0
checksums.yaml.gz.sig CHANGED
Binary file
data/.rspec CHANGED
@@ -3,3 +3,5 @@
3
3
  --exclude-pattern "spec/integrations/**/*_spec.rb"
4
4
  # Do not run pro at the same time as default specs as it would cause state conflicts
5
5
  --tag ~type:pro
6
+ # Do not run fork because they need to run in isolation
7
+ --tag ~mode:fork
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Karafka framework changelog
2
2
 
3
+ ## 2.3.2 (2024-02-16)
4
+ - **[Feature]** Provide swarm capabilities to OSS and Pro.
5
+ - **[Feature]** Provide ability to use complex strategies in DLQ (Pro).
6
+ - [Enhancement] Support using `:partition` as the partition key for ActiveJob assignments.
7
+ - [Enhancement] Expand Logger listener with swarm notifications.
8
+ - [Enhancement] Introduce K8s swarm liveness listener.
9
+ - [Enhancement] Use `Process.warmup` in Ruby 3.3+ prior to forks (in swarm) and prior to app start.
10
+ - [Enhancement] Provide `app.before_warmup` event to allow hooking code loading tools prior to final warmup.
11
+ - [Enhancement] Provide `Consumer#errors_tracker` to be able to get errors that occurred while doing complex recovery.
12
+ - [Fix] Infinite consecutive error flow with VPs and without DLQ can cause endless offsets accumulation.
13
+ - [Fix] Quieting mode causes too early unsubscribe.
14
+
15
+ ## 2.3.1 (2024-02-08)
16
+ - [Refactor] Ensure that `Karafka::Helpers::Async#async_call` can run from multiple threads.
17
+
3
18
  ## 2.3.0 (2024-01-26)
4
19
  - **[Feature]** Introduce Exactly-Once Semantics within consumers `#transaction` block (Pro)
5
20
  - **[Feature]** Provide ability to multiplex subscription groups (Pro)
data/Gemfile CHANGED
@@ -10,7 +10,7 @@ gemspec
10
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', '>= 0.8.0.rc1'
13
+ gem 'karafka-web', '>= 0.8.0'
14
14
  end
15
15
 
16
16
  group :test do
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka (2.3.0)
4
+ karafka (2.3.2)
5
5
  karafka-core (>= 2.3.0, < 2.4.0)
6
6
  waterdrop (>= 2.6.12, < 3.0.0)
7
7
  zeitwerk (~> 2.3)
@@ -27,7 +27,7 @@ GEM
27
27
  byebug (11.1.3)
28
28
  concurrent-ruby (1.2.3)
29
29
  connection_pool (2.4.1)
30
- diff-lcs (1.5.0)
30
+ diff-lcs (1.5.1)
31
31
  docile (1.4.0)
32
32
  drb (2.2.0)
33
33
  ruby2_keywords
@@ -41,36 +41,36 @@ GEM
41
41
  concurrent-ruby (~> 1.0)
42
42
  karafka-core (2.3.0)
43
43
  karafka-rdkafka (>= 0.14.8, < 0.15.0)
44
- karafka-rdkafka (0.14.8)
44
+ karafka-rdkafka (0.14.10)
45
45
  ffi (~> 1.15)
46
46
  mini_portile2 (~> 2.6)
47
47
  rake (> 12)
48
- karafka-web (0.8.0.rc1)
48
+ karafka-web (0.8.2)
49
49
  erubi (~> 1.4)
50
- karafka (>= 2.3.0.rc1, < 2.4.0)
51
- karafka-core (>= 2.3.0.rc1, < 2.4.0)
50
+ karafka (>= 2.3.0, < 2.4.0)
51
+ karafka-core (>= 2.3.0, < 2.4.0)
52
52
  roda (~> 3.68, >= 3.69)
53
53
  tilt (~> 2.0)
54
54
  mini_portile2 (2.8.5)
55
55
  minitest (5.21.2)
56
56
  mutex_m (0.2.0)
57
- rack (3.0.8)
57
+ rack (3.0.9)
58
58
  rake (13.1.0)
59
- roda (3.76.0)
59
+ roda (3.77.0)
60
60
  rack
61
- rspec (3.12.0)
62
- rspec-core (~> 3.12.0)
63
- rspec-expectations (~> 3.12.0)
64
- rspec-mocks (~> 3.12.0)
65
- rspec-core (3.12.2)
66
- rspec-support (~> 3.12.0)
67
- rspec-expectations (3.12.3)
61
+ rspec (3.13.0)
62
+ rspec-core (~> 3.13.0)
63
+ rspec-expectations (~> 3.13.0)
64
+ rspec-mocks (~> 3.13.0)
65
+ rspec-core (3.13.0)
66
+ rspec-support (~> 3.13.0)
67
+ rspec-expectations (3.13.0)
68
68
  diff-lcs (>= 1.2.0, < 2.0)
69
- rspec-support (~> 3.12.0)
70
- rspec-mocks (3.12.6)
69
+ rspec-support (~> 3.13.0)
70
+ rspec-mocks (3.13.0)
71
71
  diff-lcs (>= 1.2.0, < 2.0)
72
- rspec-support (~> 3.12.0)
73
- rspec-support (3.12.1)
72
+ rspec-support (~> 3.13.0)
73
+ rspec-support (3.13.0)
74
74
  ruby2_keywords (0.0.5)
75
75
  simplecov (0.22.0)
76
76
  docile (~> 1.1)
@@ -81,10 +81,10 @@ GEM
81
81
  tilt (2.3.0)
82
82
  tzinfo (2.0.6)
83
83
  concurrent-ruby (~> 1.0)
84
- waterdrop (2.6.12)
84
+ waterdrop (2.6.14)
85
85
  karafka-core (>= 2.2.3, < 3.0.0)
86
86
  zeitwerk (~> 2.3)
87
- zeitwerk (2.6.12)
87
+ zeitwerk (2.6.13)
88
88
 
89
89
  PLATFORMS
90
90
  ruby
@@ -95,7 +95,7 @@ DEPENDENCIES
95
95
  byebug
96
96
  factory_bot
97
97
  karafka!
98
- karafka-web (>= 0.8.0.rc1)
98
+ karafka-web (>= 0.8.0)
99
99
  rspec
100
100
  simplecov
101
101
 
data/README.md CHANGED
@@ -57,8 +57,8 @@ We also maintain many [integration specs](https://github.com/karafka/karafka/tre
57
57
  1. Add and install Karafka:
58
58
 
59
59
  ```bash
60
- # Make sure to install Karafka 2.2
61
- bundle add karafka --version ">= 2.2.10"
60
+ # Make sure to install Karafka 2.3
61
+ bundle add karafka --version ">= 2.3.0"
62
62
 
63
63
  bundle exec karafka install
64
64
  ```
data/bin/integrations CHANGED
@@ -43,7 +43,8 @@ class Scenario
43
43
  'consumption/worker_critical_error_behaviour_spec.rb' => [0, 2].freeze,
44
44
  'shutdown/on_hanging_jobs_and_a_shutdown_spec.rb' => [2].freeze,
45
45
  'shutdown/on_hanging_on_shutdown_job_and_a_shutdown_spec.rb' => [2].freeze,
46
- 'shutdown/on_hanging_listener_and_shutdown_spec.rb' => [2].freeze
46
+ 'shutdown/on_hanging_listener_and_shutdown_spec.rb' => [2].freeze,
47
+ 'swarm/forceful_shutdown_of_hanging_spec.rb' => [2].freeze
47
48
  }.freeze
48
49
 
49
50
  private_constant :MAX_RUN_TIME, :EXIT_CODES
data/bin/rspecs CHANGED
@@ -2,5 +2,9 @@
2
2
 
3
3
  set -e
4
4
 
5
- SPECS_TYPE=regular bundle exec rspec --tag ~type:pro
6
- SPECS_TYPE=pro bundle exec rspec --tag type:pro
5
+ # Run only regular non-forking specs first
6
+ SPECS_TYPE=regular bundle exec rspec --tag ~type:pro --tag ~mode:fork
7
+ # Run forking specs, they need to run in isolation not to crash because of librdkafka
8
+ SPECS_TYPE=regular bundle exec rspec --tag mode:fork
9
+ # Run pro specs at the end
10
+ SPECS_TYPE=pro bundle exec rspec --tag type:pro --tag ~mode:fork
@@ -1,11 +1,12 @@
1
1
  en:
2
2
  validations:
3
3
  config:
4
- missing: needs to be present
5
- client_id_format: 'needs to be a string with a Kafka accepted format'
6
4
  license.entity_format: needs to be a string
7
5
  license.token_format: needs to be either false or a string
8
6
  license.expires_on_format: needs to be a valid date
7
+
8
+ missing: needs to be present
9
+ client_id_format: 'needs to be a string with a Kafka accepted format'
9
10
  concurrency_format: needs to be an integer bigger than 0
10
11
  consumer_mapper_format: needs to be present
11
12
  consumer_persistence_format: needs to be either true or false
@@ -15,6 +16,10 @@ en:
15
16
  shutdown_timeout_format: needs to be an integer bigger than 0
16
17
  max_wait_time_format: needs to be an integer bigger than 0
17
18
  kafka_format: needs to be a filled hash
19
+ key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
20
+ max_timeout_vs_pause_max_timeout: pause_timeout must be less or equal to pause_max_timeout
21
+ shutdown_timeout_vs_max_wait_time: shutdown_timeout must be more than max_wait_time
22
+
18
23
  internal.processing.jobs_builder_format: cannot be nil
19
24
  internal.processing.jobs_queue_class_format: cannot be nil
20
25
  internal.processing.scheduler_class_format: cannot be nil
@@ -22,14 +27,20 @@ en:
22
27
  internal.processing.partitioner_class_format: cannot be nil
23
28
  internal.processing.strategy_selector_format: cannot be nil
24
29
  internal.processing.expansions_selector_format: cannot be nil
25
- internal.active_job.dispatcher: cannot be nil
26
- internal.active_job.job_options_contract: cannot be nil
27
- internal.active_job.consumer_class: cannot be nil
30
+
31
+ internal.active_job.dispatcher_format: cannot be nil
32
+ internal.active_job.job_options_contract_format: cannot be nil
33
+ internal.active_job.consumer_class_format: cannot be nil
34
+
28
35
  internal.status_format: needs to be present
29
36
  internal.process_format: needs to be present
30
37
  internal.tick_interval_format: needs to be an integer bigger or equal to 1000
38
+ internal.supervision_sleep_format: needs to be an integer bigger than 0
39
+ internal.forceful_exit_code_format: needs to be an integer bigger or equal to 0
40
+
31
41
  internal.routing.builder_format: needs to be present
32
42
  internal.routing.subscription_groups_builder_format: needs to be present
43
+
33
44
  internal.connection.manager_format: needs to be present
34
45
  internal.connection.conductor_format: needs to be present
35
46
  internal.connection.proxy.query_watermark_offsets.timeout_format: needs to be an integer bigger than 0
@@ -41,14 +52,25 @@ en:
41
52
  internal.connection.proxy.committed.timeout_format: needs to be an integer bigger than 0
42
53
  internal.connection.proxy.committed.max_attempts_format: needs to be an integer bigger than 0
43
54
  internal.connection.proxy.committed.wait_time_format: needs to be an integer bigger than 0
44
- key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
45
- max_timeout_vs_pause_max_timeout: pause_timeout must be less or equal to pause_max_timeout
46
- shutdown_timeout_vs_max_wait_time: shutdown_timeout must be more than max_wait_time
55
+
56
+ internal.swarm.manager_format: cannot be nil
57
+ internal.swarm.orphaned_exit_code_format: needs to be an integer bigger or equal to 0
58
+ internal.swarm.pidfd_open_syscall_format: needs to be an integer bigger or equal to 0
59
+ internal.swarm.pidfd_signal_syscall_format: needs to be an integer bigger or equal to 0
60
+ internal.swarm.supervision_interval_format: needs to be an integer bigger or equal to 1000
61
+ internal.swarm.liveness_interval_format: needs to be an integer bigger or equal to 1000
62
+ internal.swarm.liveness_listener_format: cannot be nil
63
+ internal.swarm.node_report_timeout_format: needs to be an integer bigger or equal to 1000
64
+ internal.swarm.node_restart_timeout_format: needs to be an integer bigger or equal to 1000
65
+
47
66
  admin.kafka_format: needs to be a hash
48
67
  admin.group_id_format: 'needs to be a string with a Kafka accepted format'
49
68
  admin.max_wait_time_format: 'needs to be an integer bigger than 0'
50
69
  admin.max_attempts_format: 'needs to be an integer bigger than 0'
51
70
 
71
+ swarm.nodes_format: 'needs to be an integer bigger than 0'
72
+ swarm.node_format: needs to be false or node instance
73
+
52
74
  server_cli_options:
53
75
  missing: needs to be present
54
76
  consumer_groups_inclusion: Unknown consumer group name
@@ -8,6 +8,8 @@ en:
8
8
  long_running_job.active_format: needs to be either true or false
9
9
 
10
10
  dead_letter_queue_with_virtual_partitions: when using Dead Letter Queue with Virtual Partitions, at least one retry is required.
11
+ dead_letter_queue.strategy_format: 'needs to respond to #call'
12
+ dead_letter_queue.strategy_missing: needs to be present
11
13
 
12
14
  throttling.active_format: needs to be either true or false
13
15
  throttling.limit_format: needs to be equal or more than 1
data/docker-compose.yml CHANGED
@@ -3,7 +3,7 @@ version: '2'
3
3
  services:
4
4
  kafka:
5
5
  container_name: kafka
6
- image: confluentinc/cp-kafka:7.5.3
6
+ image: confluentinc/cp-kafka:7.6.0
7
7
 
8
8
  ports:
9
9
  - 9092:9092
data/lib/karafka/app.rb CHANGED
@@ -6,6 +6,20 @@ module Karafka
6
6
  extend Setup::Dsl
7
7
 
8
8
  class << self
9
+ # Notifies the Ruby virtual machine that the boot sequence is finished, and that now is a
10
+ # good time to optimize the application. In case of older Ruby versions, runs compacting,
11
+ # which is part of the full warmup introduced in Ruby 3.3.
12
+ def warmup
13
+ # Per recommendation, this should not run in children nodes
14
+ return if Karafka::App.config.swarm.node
15
+
16
+ monitor.instrument('app.before_warmup', caller: self)
17
+
18
+ return GC.compact unless ::Process.respond_to?(:warmup)
19
+
20
+ ::Process.warmup
21
+ end
22
+
9
23
  # @return [Karafka::Routing::Builder] consumers builder instance alias
10
24
  def consumer_groups
11
25
  config
@@ -5,6 +5,8 @@ module Karafka
5
5
  # Base class for all the command that we want to define
6
6
  # This base class provides an interface to easier separate single independent commands
7
7
  class Base
8
+ include Helpers::Colorize
9
+
8
10
  # @return [Hash] given command cli options
9
11
  attr_reader :options
10
12
 
@@ -19,6 +21,23 @@ module Karafka
19
21
  raise NotImplementedError, 'Implement this in a subclass'
20
22
  end
21
23
 
24
+ private
25
+
26
+ # Prints marketing info
27
+ def print_marketing_info
28
+ Karafka.logger.info Info::BANNER
29
+
30
+ if Karafka.pro?
31
+ Karafka.logger.info(
32
+ green('Thank you for using Karafka Pro!')
33
+ )
34
+ else
35
+ Karafka.logger.info(
36
+ red('Upgrade to Karafka Pro for more features and support: https://karafka.io')
37
+ )
38
+ end
39
+ end
40
+
22
41
  class << self
23
42
  # Loads proper environment with what is needed to run the CLI
24
43
  def load
@@ -5,8 +5,6 @@ module Karafka
5
5
  class Cli
6
6
  # Server Karafka Cli action
7
7
  class Server < Base
8
- include Helpers::Colorize
9
-
10
8
  # Types of things we can include / exclude from the routing via the CLI options
11
9
  SUPPORTED_TYPES = ::Karafka::Routing::ActivityManager::SUPPORTED_TYPES
12
10
 
@@ -16,63 +14,68 @@ module Karafka
16
14
 
17
15
  aliases :s, :consumer
18
16
 
19
- option(
20
- :consumer_groups,
21
- 'Runs server only with specified consumer groups',
22
- Array,
23
- %w[
24
- -g
25
- --consumer_groups
26
- --include_consumer_groups
27
- ]
28
- )
29
-
30
- option(
31
- :subscription_groups,
32
- 'Runs server only with specified subscription groups',
33
- Array,
34
- %w[
35
- --subscription_groups
36
- --include_subscription_groups
37
- ]
38
- )
39
-
40
- option(
41
- :topics,
42
- 'Runs server only with specified topics',
43
- Array,
44
- %w[
45
- --topics
46
- --include_topics
47
- ]
48
- )
49
-
50
- option(
51
- :exclude_consumer_groups,
52
- 'Runs server without specified consumer groups',
53
- Array,
54
- %w[
55
- --exclude_consumer_groups
56
- ]
57
- )
58
-
59
- option(
60
- :exclude_subscription_groups,
61
- 'Runs server without specified subscription groups',
62
- Array,
63
- %w[
64
- --exclude_subscription_groups
65
- ]
66
- )
67
-
68
- option(
69
- :exclude_topics,
70
- 'Runs server without specified topics',
71
- Array,
72
- %w[
73
- --exclude_topics
74
- ]
75
- )
17
+ # Those options can also be used when in swarm mode, hence we re-use
18
+ OPTIONS_BUILDER = lambda do
19
+ option(
20
+ :consumer_groups,
21
+ 'Runs server only with specified consumer groups',
22
+ Array,
23
+ %w[
24
+ -g
25
+ --consumer_groups
26
+ --include_consumer_groups
27
+ ]
28
+ )
29
+
30
+ option(
31
+ :subscription_groups,
32
+ 'Runs server only with specified subscription groups',
33
+ Array,
34
+ %w[
35
+ --subscription_groups
36
+ --include_subscription_groups
37
+ ]
38
+ )
39
+
40
+ option(
41
+ :topics,
42
+ 'Runs server only with specified topics',
43
+ Array,
44
+ %w[
45
+ --topics
46
+ --include_topics
47
+ ]
48
+ )
49
+
50
+ option(
51
+ :exclude_consumer_groups,
52
+ 'Runs server without specified consumer groups',
53
+ Array,
54
+ %w[
55
+ --exclude_consumer_groups
56
+ ]
57
+ )
58
+
59
+ option(
60
+ :exclude_subscription_groups,
61
+ 'Runs server without specified subscription groups',
62
+ Array,
63
+ %w[
64
+ --exclude_subscription_groups
65
+ ]
66
+ )
67
+
68
+ option(
69
+ :exclude_topics,
70
+ 'Runs server without specified topics',
71
+ Array,
72
+ %w[
73
+ --exclude_topics
74
+ ]
75
+ )
76
+ end
77
+
78
+ instance_exec(&OPTIONS_BUILDER)
76
79
 
77
80
  # Start the Karafka server
78
81
  def call
@@ -85,8 +88,6 @@ module Karafka
85
88
  Karafka::Server.run
86
89
  end
87
90
 
88
- private
89
-
90
91
  # Registers things we want to include (if defined)
91
92
  def register_inclusions
92
93
  activities = ::Karafka::App.config.internal.routing.activity_manager
@@ -108,21 +109,6 @@ module Karafka
108
109
  names.each { |name| activities.exclude(type, name) }
109
110
  end
110
111
  end
111
-
112
- # Prints marketing info
113
- def print_marketing_info
114
- Karafka.logger.info Info::BANNER
115
-
116
- if Karafka.pro?
117
- Karafka.logger.info(
118
- green('Thank you for using Karafka Pro!')
119
- )
120
- else
121
- Karafka.logger.info(
122
- red('Upgrade to Karafka Pro for more features and support: https://karafka.io')
123
- )
124
- end
125
- end
126
112
  end
127
113
  end
128
114
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Karafka framework Cli
5
+ class Cli
6
+ # Starts swarm of consumers forked from the supervisor
7
+ class Swarm < Base
8
+ desc 'Starts swarm of Karafka consumers with a supervisor'
9
+
10
+ aliases :swarm
11
+
12
+ instance_exec(&Server::OPTIONS_BUILDER)
13
+
14
+ # Starts the swarm
15
+ def call
16
+ ::Karafka::Swarm.ensure_supported!
17
+
18
+ # Print our banner and info in the dev mode
19
+ print_marketing_info if Karafka::App.env.development?
20
+
21
+ # This will register inclusions and exclusions in the routing, so all forks will use it
22
+ server = Server.new
23
+ server.register_inclusions
24
+ server.register_exclusions
25
+
26
+ Karafka::Swarm::Supervisor.new.run
27
+ end
28
+ end
29
+ end
30
+ end
@@ -15,13 +15,13 @@ module Karafka
15
15
  # Skip verification if web is not used at all
16
16
  return unless require_version('karafka/web')
17
17
 
18
- # All good if version higher than 0.7.x because we expect 0.8.0 or higher
19
- return if version(Karafka::Web::VERSION) >= version('0.7.100')
18
+ # All good if version higher than 0.8.1 because we expect 0.8.2 or higher
19
+ return if version(Karafka::Web::VERSION) >= version('0.8.2')
20
20
 
21
21
  # If older web-ui used, we cannot allow it
22
22
  raise(
23
23
  Errors::DependencyConstraintsError,
24
- 'karafka-web < 0.8.0 is not compatible with this karafka version'
24
+ 'karafka-web < 0.8.2 is not compatible with this karafka version'
25
25
  )
26
26
  end
27
27
 
@@ -34,6 +34,11 @@ module Karafka
34
34
  required(:max_wait_time) { |val| val.is_a?(Integer) && val.positive? }
35
35
  required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
36
36
 
37
+ nested(:swarm) do
38
+ required(:nodes) { |val| val.is_a?(Integer) && val.positive? }
39
+ required(:node) { |val| val == false || val.is_a?(Karafka::Swarm::Node) }
40
+ end
41
+
37
42
  nested(:admin) do
38
43
  # Can be empty because inherits values from the root kafka
39
44
  required(:kafka) { |val| val.is_a?(Hash) }
@@ -49,6 +54,20 @@ module Karafka
49
54
  # In theory this could be less than a second, however this would impact the maximum time
50
55
  # of a single consumer queue poll, hence we prevent it
51
56
  required(:tick_interval) { |val| val.is_a?(Integer) && val >= 1_000 }
57
+ required(:supervision_sleep) { |val| val.is_a?(Numeric) && val.positive? }
58
+ required(:forceful_exit_code) { |val| val.is_a?(Integer) && val >= 0 }
59
+
60
+ nested(:swarm) do
61
+ required(:manager) { |val| !val.nil? }
62
+ required(:orphaned_exit_code) { |val| val.is_a?(Integer) && val >= 0 }
63
+ required(:pidfd_open_syscall) { |val| val.is_a?(Integer) && val >= 0 }
64
+ required(:pidfd_signal_syscall) { |val| val.is_a?(Integer) && val >= 0 }
65
+ required(:supervision_interval) { |val| val.is_a?(Integer) && val >= 1_000 }
66
+ required(:liveness_interval) { |val| val.is_a?(Integer) && val >= 1_000 }
67
+ required(:liveness_listener) { |val| !val.nil? }
68
+ required(:node_report_timeout) { |val| val.is_a?(Integer) && val >= 1_000 }
69
+ required(:node_restart_timeout) { |val| val.is_a?(Integer) && val >= 1_000 }
70
+ end
52
71
 
53
72
  nested(:connection) do
54
73
  required(:manager) { |val| !val.nil? }
@@ -87,5 +87,17 @@ module Karafka
87
87
  # Because we do not want to require web out of the box and we do not want to lock web with
88
88
  # karafka 1:1, we do such a sanity check
89
89
  DependencyConstraintsError = Class.new(BaseError)
90
+
91
+ # Raised when we were not able to open pidfd for given pid
92
+ # This should not happen. If you see it, please report.
93
+ PidfdOpenFailedError = Class.new(BaseError)
94
+
95
+ # Failed to send signal to a process via pidfd
96
+ # This should not happen. If you see it, please report.
97
+ PidfdSignalFailedError = Class.new(BaseError)
98
+
99
+ # Raised when given option/feature is not supported on a given platform or when given option
100
+ # is not supported in a given configuration
101
+ UnsupportedOptionError = Class.new(BaseError)
90
102
  end
91
103
  end
@@ -8,6 +8,12 @@ module Karafka
8
8
  # @note Thread running code needs to manage it's own exceptions. If they leak out, they will
9
9
  # abort thread on exception.
10
10
  module Async
11
+ # Mutex used to ensure we do not create multiple threads if we decide to run this
12
+ # in parallel on multiple threads
13
+ MUTEX = Mutex.new
14
+
15
+ private_constant :MUTEX
16
+
11
17
  class << self
12
18
  # Adds forwardable to redirect thread-based control methods to the underlying thread that
13
19
  # runs the async operations
@@ -22,10 +28,14 @@ module Karafka
22
28
 
23
29
  # Runs the `#call` method in a new thread
24
30
  def async_call
25
- @thread = Thread.new do
26
- Thread.current.abort_on_exception = true
31
+ MUTEX.synchronize do
32
+ return if @thread&.alive?
33
+
34
+ @thread = Thread.new do
35
+ Thread.current.abort_on_exception = true
27
36
 
28
- call
37
+ call
38
+ end
29
39
  end
30
40
  end
31
41
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Helpers
5
+ # Module allowing for configuration injections. By default injects whole app config
6
+ # Allows for granular config injection
7
+ class ConfigImporter < Module
8
+ # @param attributes [Hash<Symbol, Array<Symbol>>] map defining what we want to inject.
9
+ # The key is the name under which attribute will be visible and the value is the full
10
+ # path to the attribute
11
+ def initialize(attributes = { config: %i[itself] })
12
+ super()
13
+ @attributes = attributes
14
+ end
15
+
16
+ # @param model [Object] object to which we want to add the config fetcher
17
+ def included(model)
18
+ super
19
+
20
+ @attributes.each do |name, path|
21
+ model.class_eval <<~RUBY, __FILE__, __LINE__ + 1
22
+ def #{name}
23
+ @#{name} ||= ::Karafka::App.config.#{path.join('.')}
24
+ end
25
+ RUBY
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end