- [Enhancement] Document that a leaf's
defaultvalue is intentionally shared by reference across the class template and every config instance produced byConfigurable::Node#deep_dup. This uniform rule (the leaf is shallow-copied) is what lets a shared service object passed as a default (e.g. a logger) keep its identity across all configs; the flip side is that an in-place mutation of a mutable container default (config.list << :x) is visible on every instance. Callers that need a per-instance mutable default should assign it inside aconfigureblock or dup it themselves rather than relying on a mutabledefault:(e.g.default: []). Adds characterization tests covering the shared-default behavior. - [Fix]
require "pathname"explicitly inlib/karafka-core.rb(with the other top-level requires).Karafka::Core.gem_rootreturns aPathname, but the gem never requiredpathname-- it only worked because Bundler (or another gem) happened to load it first. In an environment where nothing else loads it,gem_rootraisedNameError: uninitialized constant Pathname. - [Enhancement] Document the
virtualrule result contract: a rule must return a freshly builtArrayof[path, message]error pairs on every call.Contract#calltakes ownership of that array and prepends the current scope onto each pair in place (avoiding a per-error allocation), so returning a memoized, shared or frozen array is unsupported -- the in-place scoping would accumulate the scope prefix across validations or raiseFrozenError. Adds characterization tests for the supported and unsupported patterns. - [Fix]
Configurable::Node#registerraises the documented "already registered"ArgumentErrorfor 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, soregistersilently overwrote it; it now checks the defined children. - [Fix]
Contractable::Contract.nestednow pops its path in anensure. 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. - [Fix]
Contract#callno longer raisesNoMethodErrorwhen 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). - [Fix] Honor
excluded_keyscontaining"cgrp"inStatisticsDecoratoronly_keysmode. Thecgrpbranch of the structure-aware fast path lacked the exclusion guard that thebrokersandtopicsbranches have, so excluding the consumer-group subtree still decorated it (inconsistent with the full-decoration path). - [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); callingrd_kafka_nameon it dereferenced the null pointer and could segfault the process. Mirrors the upstreamErrorCallback. - [Fix] Resolve fatal errors in the patched rdkafka error callback.
ERR__FATALis only a generic marker, so the callback now fetches the real underlying error code and description viaRdkafkaError.build_fatal(rd_kafka_fatal_error) instead of reporting the generic fatal code. Mirrors the upstreamErrorCallback. - [Fix] A lazy setting declared without a constructor (
setting(:x, lazy: true)) no longer raises when its accessor is read.lazy: trueonly makes sense together with a constructor to (re)evaluate; without one there is nothing to evaluate, so such a setting now behaves like a regular setting backed by its default. Previously reading it raisedNoMethodError—nil.aritythrough the dynamic accessor for a falsy default, or a missing accessor for a truthy default. - [Fix]
Contract#callno longer raisesNoMethodErrorwhen a virtual rule returnsfalse. A virtual rule now signals "no errors" with any non-Array result (true/false/nil); only anArrayof error pairs is collected. Previously afalsereturn reachedfalse.each(anilreturn was already tolerated). - [Fix] Manage
CallbacksManagercallbacks copy-on-write:add/deleterebuild and atomically swap an immutable values snapshot under a mutex, and#calliterates that snapshot directly. The previous values cache (introduced in 2.5.11) was lazily invalidated from within#call, which could not be done atomically against a concurrentadd/delete, so a callback racing with dispatch could be silently lost — a removed one kept firing forever, or a newly added one never fired. The copy-on-write read takes no lock and allocates nothing per call, so the race is fixed without reintroducing a per-call#valuesallocation. librdkafka statistics/error callbacks fire from a background thread while callbacks are registered/unregistered, so this was reachable in practice. - [Fix] Manage
Notificationssubscriptions copy-on-write (#subscribe,#unsubscribeand#clearreplace the per-event listener array instead of mutating it in place) so a listener that unsubscribes itself (or another) from within its own handler no longer causes the listener following it to be silently skipped, and concurrent subscribe/unsubscribe during dispatch is safe. Dispatch keeps iterating the live array directly, so there is no per-notification allocation on the hot path. - [Fix] Report a freeze duration (
_fd) of0for statistics keys that are newly introduced in an emission (e.g. a broker or partition that appears mid-stream) instead of the elapsed time since the previous emission. A key that did not exist in the prior emission could not have been "frozen" for any duration, so accumulating the inter-emission gap was incorrect and also made the relatedStatisticsDecoratorspec flaky on slow CIs (_fddepended on the wall-clock gap between the two emissions). - [Fix] Make assigning a setting on a frozen
Configurable::Nodeatomic. The ivar-backed writer evaluated@configs_refs[name] = valuebeforeinstance_variable_set, so a frozen node mutated the canonical store and only then raisedFrozenError, leaving the store and the ivar-backed reader permanently out of sync. It now raises before touching any state. - [Fix]
Configurable::Node#to_hnow evaluates a setting's constructor with its default (arity-aware, matching#compile) instead of calling it with no arguments. The documented->(default) { ... }constructor form previously raisedArgumentError: wrong number of argumentsfrom#to_hwhenever the value was not yet in the config store (e.g.#to_hon an unconfigured instance, or an unread lazy setting). - [Fix] Honor
excluded_keysinsideStatisticsDecoratoronly_keysdecoration. A key listed in bothonly_keysandexcluded_keyswas still decorated because the direct-access decoration loop never consultedexcluded_keys; exclusion now wins, matching the full-decoration path. - [Fix] Strip the tests/specs root directory as an anchored prefix (
sub(/\A.../)) instead of a globalgsubinMinitestLocatorandRSpecLocator. 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.