karafka-core 2.6.3 → 2.6.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70aa3940193aebd6b4f232950a2b765ee57025aea6b3d3d7c72234429f904de4
4
- data.tar.gz: e4a550006b517eea864370ef12534dacdb6fddae40f3adf7308981fa99a1e7a1
3
+ metadata.gz: b7064756fbbf13ddd1048da55f30969b2ae10d58442b9a4ec8c8b5250a260473
4
+ data.tar.gz: 9d6fbe9c4257ce42e03017333602354265c6fa408e585099c6c515d3523c368d
5
5
  SHA512:
6
- metadata.gz: '05908e99fd999ee007bcb3efd46a6a3b949d88e40d980c5ca4e8aa90a3c312755717dc8feef7a7de42c5c7c235e3ac623e26b91e9a3456568e4d552a2d8782ea'
7
- data.tar.gz: 61456523b5822db6c78ad36c839ed6e3bdcbd9bc18d56cebfb95947443296aad94b917b3bef7798f093b471d703ae9d5a7f43d1b09d726ea655e0b2a0b091a3c
6
+ metadata.gz: 4889fd25e48a9f06adfe9797862aa7d52dd6f363cb95839bf31e4bf66b499db9210331d1d5302f9bdf8d221c9895e68dcc58b8aec07b52004c21e7796779ed48
7
+ data.tar.gz: b154d18d6feeefac2e715d21c0f63c7e5841c145f5f13dbd588386d7dbd54cc0187c2640a1034679b806db66dc6e19ffb8bf5007ca44c914a0e69abb8758f356
data/CHANGELOG.md CHANGED
@@ -1,36 +1,39 @@
1
1
  # Karafka Core Changelog
2
2
 
3
+ ## 2.6.4 (2026-09-11)
4
+ - [Enhancement] Add `Configurable::Importer`, a subclassable base for injecting memoized config readers into a class. A subclass names the config root once by overriding `.root`, and each use site then names only the attributes it wants. Supports both `include` and `extend`.
5
+
3
6
  ## 2.6.3 (2026-08-31)
4
7
  - [Enhancement] Add `Configurable::Injector`, a reusable base for injecting default values into a config hash without overwriting user-provided settings, with support for layering extra defaults on top.
5
8
 
6
9
  ## 2.6.2 (2026-06-29)
7
10
  - [Enhancement] Document that a leaf's `default` value is shared by reference across all config instances, so a mutable default (e.g. `default: []`) mutated in place is visible everywhere. Assign it inside a `configure` block if you need a per-instance copy.
8
- - [Fix] `require "pathname"` explicitly in `lib/karafka-core.rb` (with the other top-level requires). `Karafka::Core.gem_root` returns a `Pathname`, but the gem never required `pathname` -- it only worked because Bundler (or another gem) happened to load it first. In an environment where nothing else loads it, `gem_root` raised `NameError: uninitialized constant Pathname`.
11
+ - [Fix] `require "pathname"` explicitly in `lib/karafka-core.rb`. `Karafka::Core.gem_root` returns a `Pathname` but the gem never required it, so `gem_root` raised `NameError` wherever nothing else happened to load it first.
9
12
  - [Enhancement] Document that a `virtual` rule must return a freshly built `Array` of `[path, message]` error pairs on each call; a memoized, shared or frozen array is unsupported.
10
- - [Fix] `Configurable::Node#register` raises the documented "already registered" `ArgumentError` for a name already used by an unread lazy-with-constructor setting. The duplicate guard only checked `@configs_refs`, but a lazy setting with a constructor is absent from it until first read, so `register` silently overwrote it; it now checks the defined children.
13
+ - [Fix] `Configurable::Node#register` now raises the documented "already registered" `ArgumentError` for a name used by an unread lazy-with-constructor setting, which it previously overwrote silently.
11
14
  - [Fix] `Contractable::Contract.nested` now pops its path in an `ensure`. If the block raised while the contract was being defined and the caller rescued it, the path stayed on the nesting stack and was prefixed onto every rule defined afterwards.
12
15
  - [Fix] `Contract#call` no longer raises `NoMethodError` when validating a non-Hash root with a 1-key or 2-key rule path; it reports the path as missing, consistent with the 3+-key path (and the non-Hash intermediate handling added in 2.6.1).
13
- - [Fix] Honor `excluded_keys` containing `"cgrp"` in `StatisticsDecorator` `only_keys` mode. The `cgrp` branch of the structure-aware fast path lacked the exclusion guard that the `brokers` and `topics` branches have, so excluding the consumer-group subtree still decorated it (inconsistent with the full-decoration path).
14
- - [Fix] Guard the patched rdkafka error callback against a null client pointer. librdkafka can invoke the error callback with a NULL `rd_kafka_t` (e.g. very early in client construction); calling `rd_kafka_name` on it dereferenced the null pointer and could segfault the process. Mirrors the upstream `ErrorCallback`.
15
- - [Fix] Resolve fatal errors in the patched rdkafka error callback. `ERR__FATAL` is only a generic marker, so the callback now fetches the real underlying error code and description via `RdkafkaError.build_fatal` (`rd_kafka_fatal_error`) instead of reporting the generic fatal code. Mirrors the upstream `ErrorCallback`.
16
+ - [Fix] Honor `excluded_keys` containing `"cgrp"` in `StatisticsDecorator` `only_keys` mode; the `cgrp` branch lacked the exclusion guard its `brokers` and `topics` siblings have.
17
+ - [Fix] Guard the patched rdkafka error callback against a null client pointer, which librdkafka can pass early in client construction and which could segfault the process.
18
+ - [Fix] Resolve fatal errors in the patched rdkafka error callback via `RdkafkaError.build_fatal`, so the real underlying error is reported instead of the generic `ERR__FATAL` marker.
16
19
  - [Fix] A lazy setting declared without a constructor (`setting(:x, lazy: true)`) no longer raises when read; it now behaves like a regular setting backed by its default.
17
- - [Fix] `Contract#call` no longer raises `NoMethodError` when a virtual rule returns `false`. A virtual rule now signals "no errors" with any non-Array result (`true`/`false`/`nil`); only an `Array` of error pairs is collected. Previously a `false` return reached `false.each` (a `nil` return was already tolerated).
18
- - [Fix] Make `CallbacksManager` thread-safe under concurrent `add`/`delete` during dispatch. A callback registered or removed while callbacks were firing could be lost — kept firing after removal, or never fired after being added. librdkafka fires these from a background thread, so it was reachable in practice.
20
+ - [Fix] `Contract#call` no longer raises `NoMethodError` when a virtual rule returns `false`. A virtual rule now signals "no errors" with any non-Array result; only an `Array` of error pairs is collected.
21
+ - [Fix] Make `CallbacksManager` thread-safe under concurrent `add`/`delete` during dispatch, so a callback registered or removed while callbacks were firing is no longer lost.
19
22
  - [Fix] Make `Notifications` subscriptions thread-safe: a listener that unsubscribes itself (or another) from within its own handler no longer causes the next listener to be skipped, and concurrent subscribe/unsubscribe during dispatch is now safe.
20
23
  - [Fix] Report a freeze duration (`_fd`) of `0` for statistics keys that first appear mid-stream (e.g. a new broker or partition) instead of the time since the previous emission, which was incorrect and made the related spec flaky.
21
- - [Fix] Make assigning a setting on a frozen `Configurable::Node` atomic. The ivar-backed writer evaluated `@configs_refs[name] = value` before `instance_variable_set`, so a frozen node mutated the canonical store and only then raised `FrozenError`, leaving the store and the ivar-backed reader permanently out of sync. It now raises before touching any state.
24
+ - [Fix] Make assigning a setting on a frozen `Configurable::Node` atomic; it previously mutated the canonical store before raising `FrozenError`, leaving the store and the reader permanently out of sync.
22
25
  - [Fix] `Configurable::Node#to_h` no longer raises for a setting using the `->(default) { ... }` constructor form when its value has not been set yet (e.g. `#to_h` on an unconfigured instance or an unread lazy setting).
23
26
  - [Fix] Honor `excluded_keys` inside `StatisticsDecorator` `only_keys` decoration. A key listed in both `only_keys` and `excluded_keys` was still decorated because the direct-access decoration loop never consulted `excluded_keys`; exclusion now wins, matching the full-decoration path.
24
- - [Fix] Strip the tests/specs root directory as an anchored prefix (`sub(/\A.../)`) instead of a global `gsub` in `MinitestLocator` and `RSpecLocator`. When the root directory string recurred later in a test/spec file path, the global replace removed every occurrence and corrupted the derived subject class path; only the leading prefix is now removed.
27
+ - [Fix] Strip the tests/specs root directory as an anchored prefix instead of a global `gsub` in `MinitestLocator` and `RSpecLocator`, which corrupted the derived subject path when the root string recurred later in it.
25
28
 
26
29
  ## 2.6.1 (2026-06-15)
27
30
  - [Enhancement] Speed up `Contract#call` by ~1.25–1.4x, the per-message validation path in WaterDrop producers.
28
31
  - [Fix] `Contract#call` with rule paths of 3+ keys no longer raises `NoMethodError` when an intermediate value is not a `Hash` and reports the path as missing instead, consistent with the 2-key path behavior.
29
- - [Change] Reject reserved setting names with an `ArgumentError` in `Configurable::Node#setting` and `#register`: internal state names (`node_name`, `children`, `nestings`, `compiled`, `configs_refs`, `local_defs`) and the node public API names (`setting`, `configure`, `to_h`, `deep_dup`, `register`, `compile`). Previously such names silently shadowed the node own accessors, breaking `deep_dup` or `to_h`, and assignments like `config.children = value` corrupted the node internal state.
32
+ - [Change] Reject reserved setting names with an `ArgumentError` in `Configurable::Node#setting` and `#register` (internal state names and the node public API names). Such names previously shadowed the node's own accessors and could corrupt its internal state.
30
33
  - [Enhancement] Speed up `Monitor#instrument` dispatch (~1.2x) on the common no-subscribers path.
31
34
  - [Enhancement] Speed up `Configurable::Node` settings reads (~1.4x flat, ~1.6x nested) on hot paths.
32
- - [Enhancement] Instantiate each `Configurable::Node` through a per-layout anonymous subclass so the ivar-backed settings do not grow object shape variations on the shared `Node` class (which would degrade ivar access and trigger Ruby performance warnings). `deep_dup` reuses the template's subclass, so duplicated configs share object shapes.
33
- - [Fix] Symbolize setting names at definition time (`setting`, same as `register`) and on config store writes so `String` setting names work end to end (accessors, `#to_h`, recompilation state) and cannot corrupt node internal state when matching reserved internal names (previously string-named settings were quietly broken as accessors and the store disagreed on the key type).
35
+ - [Enhancement] Instantiate each `Configurable::Node` through a per-layout anonymous subclass so ivar-backed settings do not grow object shape variations on the shared `Node` class. `deep_dup` reuses the template's subclass.
36
+ - [Fix] Symbolize setting names at definition time and on config store writes, so `String` setting names work end to end and cannot corrupt node internal state when they match reserved internal names.
34
37
  - [Change] Config nodes are now instances of anonymous `Node` subclasses: `is_a?(Karafka::Core::Configurable::Node)` still holds, but `instance_of?(Node)` is now `false` and `node.class.name` is `nil`.
35
38
  - [Change] Assigning a setting on a frozen config node now raises `FrozenError` (previously the write silently mutated internal storage despite the freeze).
36
39
 
@@ -55,7 +58,7 @@
55
58
  - [Enhancement] Store execution time separately in `Event` and build the merged payload hash lazily on `#payload` access, eliminating 1 Hash allocation per `Notifications#instrument` call when listeners use `#[]` access (the common pattern), yielding ~1.7x faster event dispatch.
56
59
  - [Enhancement] Replace `StatisticsDecorator#diff` pending-writes buffer with `keys.each` direct-write iteration, eliminating the buffer and write-back loop for ~13% faster decoration at scale (10 brokers, 20 topics, 2000 partitions).
57
60
  - [Enhancement] Reorder `StatisticsDecorator#diff` type checks to test `Numeric` before `Hash`, matching the ~80% numeric value distribution in librdkafka statistics.
58
- - [Enhancement] Support `only_keys` option in `StatisticsDecorator` to decorate only specified numeric keys (e.g. `consumer_lag`, `committed_offset`). When combined with `excluded_keys`, reduces decoration cost from ~80ms to ~8.5ms per call on large clusters (10 brokers, 20 topics, 2000 partitions) by using structure-aware navigation of the librdkafka statistics tree and direct key access instead of full-hash iteration.
61
+ - [Enhancement] Support an `only_keys` option in `StatisticsDecorator` to decorate only specified numeric keys. Combined with `excluded_keys`, this cuts decoration cost from ~80ms to ~8.5ms per call on large clusters (10 brokers, 20 topics, 2000 partitions).
59
62
  - [Enhancement] Cache `Tags#to_a` values array and invalidate on `add`/`delete`/`clear` to avoid allocating a new Array and running `uniq` on every call, yielding ~7x faster reads at 5 tags and ~28x faster at 20 tags.
60
63
 
61
64
  ## 2.5.10 (2026-03-02)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka-core (2.6.3)
4
+ karafka-core (2.6.4)
5
5
  karafka-rdkafka (>= 0.20.0)
6
6
  logger (>= 1.6.0)
7
7
 
@@ -22,7 +22,7 @@ GEM
22
22
  ffi (1.17.4-x86_64-darwin)
23
23
  ffi (1.17.4-x86_64-linux-gnu)
24
24
  ffi (1.17.4-x86_64-linux-musl)
25
- io-console (0.8.2)
25
+ io-console (0.9.2)
26
26
  json (2.21.2)
27
27
  karafka-rdkafka (0.28.0)
28
28
  ffi (~> 1.17.1)
@@ -67,7 +67,7 @@ GEM
67
67
  prism (~> 1.5)
68
68
  prism (1.9.0)
69
69
  rake (13.4.2)
70
- reline (0.6.3)
70
+ reline (0.7.0)
71
71
  io-console (~> 0.5)
72
72
  simplecov (1.1.1)
73
73
  warning (1.6.0)
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Core
5
+ module Configurable
6
+ # Base class for config importers.
7
+ #
8
+ # An importer builds memoized config readers on a target, so a class can read a few config
9
+ # values under short, local names instead of spelling out the full config path at every
10
+ # call site.
11
+ #
12
+ # The config root is not hardcoded and is not passed per use site: it is defined once, by
13
+ # overriding {root} in a subclass. Each consumer therefore ships one importer subclass
14
+ # pointing at its own config tree (e.g. `Karafka::App.config` or `Karafka::Web.config`),
15
+ # and every use site only names the attributes it wants.
16
+ #
17
+ # {root} is a method rather than a stored config object, so it is resolved lazily, on the
18
+ # first read of a given attribute. That matters because importers are typically built while
19
+ # a class body is being loaded, which is usually before the config itself has been set up.
20
+ #
21
+ # Each attribute is memoized per target, so the path is walked only once and every later
22
+ # read is a plain instance variable lookup. Memoization is `||=`-based, so an attribute
23
+ # whose configured value is `nil` or `false` is re-read on each call.
24
+ #
25
+ # @example Define the importer once, with the root
26
+ # module Karafka
27
+ # module Web
28
+ # module Helpers
29
+ # class ConfigImporter < Karafka::Core::Configurable::Importer
30
+ # class << self
31
+ # def root
32
+ # Karafka::Web.config
33
+ # end
34
+ # end
35
+ # end
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ # @example Import instance-level readers
41
+ # class LagStats
42
+ # include Karafka::Web::Helpers::ConfigImporter.new(
43
+ # skew_threshold: %i[ui health lags skew_threshold]
44
+ # )
45
+ #
46
+ # def skewed?(lag)
47
+ # lag > skew_threshold
48
+ # end
49
+ # end
50
+ #
51
+ # @example Import class-level readers
52
+ # class Message
53
+ # extend Karafka::Web::Helpers::ConfigImporter.new(per_page: %i[ui per_page])
54
+ # end
55
+ #
56
+ # Message.per_page
57
+ #
58
+ # @example Import the whole config root under `#config` (the default)
59
+ # class Consumer
60
+ # include Karafka::Web::Helpers::ConfigImporter.new
61
+ # end
62
+ #
63
+ # @note The base class defines no root of its own; it is not useful directly and is expected
64
+ # to be subclassed. Reading an attribute off an importer whose class defines no {root}
65
+ # raises `NotImplementedError`.
66
+ #
67
+ # @note The readers are defined directly on the target, so an attribute name that collides
68
+ # with an existing method silently replaces it.
69
+ class Importer < Module
70
+ # Default attributes map, exposing the whole config root under `#config`.
71
+ DEFAULT_ATTRIBUTES = { config: %i[itself] }.freeze
72
+
73
+ private_constant :DEFAULT_ATTRIBUTES
74
+
75
+ class << self
76
+ # @return [Object] config root the attribute paths are resolved against. Override in
77
+ # subclasses; the base class defines none. Called on each memoization miss rather
78
+ # than when the importer is built, so it may safely reference a config that does not
79
+ # exist yet at load time.
80
+ # @raise [NotImplementedError] when called on a class that does not define a root
81
+ def root
82
+ raise(
83
+ NotImplementedError,
84
+ "Define .root in a Karafka::Core::Configurable::Importer subclass"
85
+ )
86
+ end
87
+ end
88
+
89
+ # @param attributes [Hash{Symbol => Array<Symbol>}] map defining what to import. The key
90
+ # is the name the reader will be available under and the value is the path to the
91
+ # attribute, relative to the root, as an array of message names.
92
+ def initialize(attributes = DEFAULT_ATTRIBUTES)
93
+ super()
94
+ @attributes = attributes
95
+ end
96
+
97
+ # @return [Object] config root this importer resolves against, as defined by its class
98
+ def root
99
+ self.class.root
100
+ end
101
+
102
+ # Defines the readers as instance methods on the target.
103
+ #
104
+ # @param model [Object] object to which we want to add the config fetchers
105
+ # @return [void]
106
+ def included(model)
107
+ super
108
+
109
+ define_readers(model, :define_method)
110
+ end
111
+
112
+ # Defines the readers as class-level methods on the target.
113
+ #
114
+ # @param model [Object] object to which we want to add the config fetchers on a class
115
+ # level
116
+ # @return [void]
117
+ def extended(model)
118
+ super
119
+
120
+ define_readers(model, :define_singleton_method)
121
+ end
122
+
123
+ private
124
+
125
+ # Defines one memoized reader per configured attribute on the target.
126
+ #
127
+ # @param model [Object] object the readers are defined on
128
+ # @param definer [Symbol] method used to define them, either `:define_method` for
129
+ # instance-level readers or `:define_singleton_method` for class-level ones
130
+ # @return [void]
131
+ def define_readers(model, definer)
132
+ importer = self
133
+
134
+ @attributes.each do |name, path|
135
+ ivar = :"@#{name}"
136
+
137
+ model.public_send(definer, name) do
138
+ instance_variable_get(ivar) || instance_variable_set(
139
+ ivar,
140
+ path.reduce(importer.root) { |node, part| node.public_send(part) }
141
+ )
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -3,6 +3,6 @@
3
3
  module Karafka
4
4
  module Core
5
5
  # Current Karafka::Core version. We follow the versioning schema of given Karafka version
6
- VERSION = "2.6.3"
6
+ VERSION = "2.6.4"
7
7
  end
8
8
  end
data/lib/karafka-core.rb CHANGED
@@ -16,6 +16,7 @@ require "karafka/core/configurable"
16
16
  require "karafka/core/configurable/leaf"
17
17
  require "karafka/core/configurable/node"
18
18
  require "karafka/core/configurable/injector"
19
+ require "karafka/core/configurable/importer"
19
20
  require "karafka/core/contractable/contract"
20
21
  require "karafka/core/contractable/result"
21
22
  require "karafka/core/contractable/rule"
data/package-lock.json CHANGED
@@ -103,9 +103,9 @@
103
103
  }
104
104
  },
105
105
  "node_modules/fastq": {
106
- "version": "1.20.1",
107
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
108
- "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
106
+ "version": "1.20.2",
107
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.2.tgz",
108
+ "integrity": "sha512-UpGiiODyCGprM8EPP6JodP6jC9Rws6TCuiDOD+nn0CJhR8guI3g/ozo4ugL0vJ+Yz1UtJuuRPqvQuybVOF1VQA==",
109
109
  "dev": true,
110
110
  "license": "ISC",
111
111
  "dependencies": {
@@ -286,9 +286,9 @@
286
286
  }
287
287
  },
288
288
  "node_modules/smol-toml": {
289
- "version": "1.7.1",
290
- "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz",
291
- "integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==",
289
+ "version": "1.8.0",
290
+ "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.8.0.tgz",
291
+ "integrity": "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==",
292
292
  "dev": true,
293
293
  "license": "BSD-3-Clause",
294
294
  "engines": {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: karafka-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.3
4
+ version: 2.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -72,6 +72,7 @@ files:
72
72
  - lib/karafka-core.rb
73
73
  - lib/karafka/core.rb
74
74
  - lib/karafka/core/configurable.rb
75
+ - lib/karafka/core/configurable/importer.rb
75
76
  - lib/karafka/core/configurable/injector.rb
76
77
  - lib/karafka/core/configurable/leaf.rb
77
78
  - lib/karafka/core/configurable/node.rb