@@ -14,13 +14,12 @@ class Metric
1414 # Initialize a new metric.
1515 #
1616 # @parameter name [Symbol] The field name for this metric.
17- # @parameter registry [Registry] The registry instance to use.
18- def initialize ( name , registry )
17+ def initialize ( name )
1918 @name = name . to_sym
20- @registry = registry
2119 @value = 0
22- @cache_valid = false
23- @cached_field_info = nil
20+
21+ @observer = nil
22+ @cached_field = nil
2423 @cached_buffer = nil
2524 @guard = Mutex . new
2625 end
@@ -34,19 +33,43 @@ def initialize(name, registry)
3433 # @attribute [Mutex] The mutex for thread safety.
3534 attr :guard
3635
37- # Invalidate the cached field information .
36+ # Set the observer and rebuild cache .
3837 #
39- # Called when the observer changes to force cache rebuild.
40- def invalidate
41- @cache_valid = false
42- @cached_field_info = nil
43- @cached_buffer = nil
38+ # This is called when the registry assigns a new observer (or removes it).
39+ # The cache is invalidated and then immediately recomputed so that the
40+ # fast write path doesn't need to re-check the observer on the first write.
41+ #
42+ # @parameter observer [#set] The new observer (or nil).
43+ def observer = ( observer )
44+ @guard . synchronize do
45+ @observer = observer
46+
47+ # Eagerly validate so the first write is fast.
48+ outcome = :no_observer
49+ if @observer
50+ if field = @observer . schema [ @name ]
51+ if buffer = @observer . buffer
52+ @cached_field = field
53+ @cached_buffer = buffer
54+ outcome = :cached
55+ else
56+ outcome = :no_buffer
57+ end
58+ else
59+ outcome = :missing_field_in_schema
60+ end
61+ else
62+ outcome = :unsupported_observer
63+ end
64+
65+ # Console.info(self, "Cache validation", metric: @name, outcome: outcome)
66+
67+ write_direct ( @value )
68+ end
4469 end
4570
4671 # Increment the metric value.
4772 #
48- # Uses the fast path (direct buffer write) when cache is valid and observer is available.
49- #
5073 # @returns [Integer] The new value of the field.
5174 def increment
5275 @guard . synchronize do
@@ -103,28 +126,6 @@ def set(value)
103126
104127 protected
105128
106- # Check if the cache is valid and rebuild if necessary.
107- #
108- # Always attempts to build the cache if it's invalid. Returns true if cache
109- # is now valid (observer exists, field is in schema, and buffer is available), false otherwise.
110- #
111- # @returns [bool] True if cache is valid, false otherwise.
112- def ensure_cache_valid!
113- unless @cache_valid
114- if observer = @registry . observer
115- if field = observer . schema [ @name ]
116- if buffer = observer . buffer
117- @cached_field_info = field
118- @cached_buffer = buffer
119- end
120- end
121- end
122-
123- # Once we've validated the cache, even if there was no observer or buffer, we mark it as valid, so that we don't try to revalidate it again:
124- @cache_valid = true
125- end
126- end
127-
128129 # Write directly to the cached buffer if available.
129130 #
130131 # This is the fast path that avoids hash lookups. Always ensures cache is valid
@@ -133,16 +134,12 @@ def ensure_cache_valid!
133134 # @parameter value [Numeric] The value to write.
134135 # @returns [Boolean] Whether the write succeeded.
135136 def write_direct ( value )
136- self . ensure_cache_valid!
137-
138137 if @cached_buffer
139- @cached_buffer . set_value ( @cached_field_info . type , @cached_field_info . offset , value )
138+ @cached_buffer . set_value ( @cached_field . type , @cached_field . offset , value )
140139 end
141140
142141 return true
143142 rescue => error
144- # If write fails, log warning but don't invalidate cache
145- # The error might be transient, and invalidating would force hash lookups
146143 Console . warn ( self , "Failed to write metric value!" , metric : { name : @name , value : value } , exception : error )
147144
148145 return false
0 commit comments