Skip to content

Commit f8a03dc

Browse files
committed
feat: add bound_count to TrackerEntry for bound instrument persistence
Add bound_count field to TrackerEntry to track live bound instrument handles. Entries with bound_count > 0 are never evicted during delta collection, ensuring bound handles always point to a live tracker. Moved from #3420 per review feedback — bound_count belongs with the bound instruments feature, not the delta collection refactor.
1 parent db7ab0d commit f8a03dc

1 file changed

Lines changed: 15 additions & 5 deletions

File tree

  • opentelemetry-sdk/src/metrics/internal

opentelemetry-sdk/src/metrics/internal/mod.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
6367
pub(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

6873
impl<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

Comments
 (0)