@@ -55,21 +55,27 @@ pub(crate) trait Aggregator {
5555 fn clone_and_reset ( & self , init : & Self :: InitConfig ) -> Self ;
5656}
5757
58- /// Wraps an aggregator with status tracking for delta collection.
58+ /// Wraps an aggregator with status tracking for delta collection and bound instruments .
5959///
6060/// `has_been_updated` tracks whether the aggregator received measurements since the last
6161/// collection cycle. This enables in-place delta collection: only updated entries are exported,
62- /// and stale entries are evicted to prevent unbounded memory growth.
62+ /// and stale unbound entries are evicted to prevent unbounded memory growth.
63+ ///
64+ /// `bound_count` tracks how many bound instrument handles reference this entry. Entries with
65+ /// bound_count > 0 are never evicted from the map, even if they had no updates in a cycle
66+ /// (they simply produce no export). This ensures bound handles always point to a live tracker.
6367pub ( crate ) struct TrackerEntry < A : Aggregator > {
6468 pub ( crate ) aggregator : A ,
6569 pub ( crate ) has_been_updated : AtomicBool ,
70+ pub ( crate ) bound_count : AtomicUsize ,
6671}
6772
6873impl < A : Aggregator > TrackerEntry < A > {
6974 fn new ( config : & A :: InitConfig ) -> Self {
7075 TrackerEntry {
7176 aggregator : A :: create ( config) ,
7277 has_been_updated : AtomicBool :: new ( false ) ,
78+ bound_count : AtomicUsize :: new ( 0 ) ,
7379 }
7480 }
7581}
@@ -288,7 +294,9 @@ where
288294
289295 /// Iterate through all attribute sets in-place, populate `DataPoints` and reset.
290296 /// Only entries updated since the last collection (tracked via `has_been_updated`)
291- /// are exported. Stale entries are evicted to prevent unbounded memory growth.
297+ /// are exported. Stale unbound entries are evicted to prevent unbounded memory growth.
298+ /// Bound entries (bound_count > 0) are never evicted — they persist until explicitly
299+ /// unbound, but produce no export when they have no updates.
292300 ///
293301 /// Used for synchronous instruments (Counter, Histogram, etc.) in Delta temporality mode.
294302 pub ( crate ) fn collect_and_reset < Res , MapFn > ( & self , dest : & mut Vec < Res > , mut map_fn : MapFn )
@@ -317,8 +325,10 @@ where
317325 if seen. insert ( Arc :: as_ptr ( tracker) ) {
318326 if tracker. has_been_updated . swap ( false , Ordering :: Relaxed ) {
319327 dest. push ( map_fn ( attrs. clone ( ) , & tracker. aggregator ) ) ;
320- } else if attrs. as_slice ( ) != overflow_attrs. as_slice ( ) {
321- // Stale — candidate for eviction
328+ } else if attrs. as_slice ( ) != overflow_attrs. as_slice ( )
329+ && tracker. bound_count . load ( Ordering :: Relaxed ) == 0
330+ {
331+ // Stale and not bound — candidate for eviction
322332 stale_entries. push ( Arc :: clone ( tracker) ) ;
323333 }
324334 }
0 commit comments