Skip to content

Commit 73c7e96

Browse files
committed
feat(nodedb-mem): add over-release counter to Budget and Governor
Budget.release() saturates the allocated counter to zero on over-release, which hides accounting drift from Budget.allocated(). The new over_release_count atomic tracks every event where released > allocated so call-sites can be identified after the fact. Governor.total_over_release_count() aggregates the counter across all per-engine budgets.
1 parent 501a854 commit 73c7e96

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

nodedb-mem/src/budget.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ pub struct Budget {
2626

2727
/// Number of times an allocation was rejected due to budget exhaustion.
2828
rejection_count: AtomicUsize,
29+
30+
/// Number of `release(size)` calls where `size > current` — the
31+
/// accounting-drift symptom. Saturating the per-engine counter
32+
/// to zero hides the drift from `allocated()`, so this counter
33+
/// is the only observable signal that the call-site is
34+
/// over-releasing.
35+
over_release_count: AtomicUsize,
2936
}
3037

3138
impl Budget {
@@ -36,6 +43,7 @@ impl Budget {
3643
allocated: Arc::new(AtomicUsize::new(0)),
3744
peak: AtomicUsize::new(0),
3845
rejection_count: AtomicUsize::new(0),
46+
over_release_count: AtomicUsize::new(0),
3947
}
4048
}
4149

@@ -110,6 +118,7 @@ impl Budget {
110118
) {
111119
Ok(_) => {
112120
if size > current {
121+
self.over_release_count.fetch_add(1, Ordering::Relaxed);
113122
tracing::warn!(
114123
released = size,
115124
allocated = current,
@@ -133,6 +142,15 @@ impl Budget {
133142
self.limit.load(Ordering::Relaxed)
134143
}
135144

145+
/// Number of over-release events observed on this budget. A
146+
/// non-zero value is the smoking-gun signal that some call-site
147+
/// is releasing more bytes than it reserved. Per-engine
148+
/// `allocated()` saturates to zero on over-release, so this
149+
/// counter is the only post-hoc observable.
150+
pub fn over_release_count(&self) -> usize {
151+
self.over_release_count.load(Ordering::Relaxed)
152+
}
153+
136154
/// Remaining bytes available.
137155
pub fn available(&self) -> usize {
138156
let limit = self.limit();

nodedb-mem/src/governor.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,16 @@ impl MemoryGovernor {
399399
self.budgets.values().map(|b| b.allocated()).sum()
400400
}
401401

402+
/// Total number of over-release events observed across all
403+
/// per-engine budgets. A non-zero value signals at least one
404+
/// call-site is releasing more bytes than it reserved — the
405+
/// "memory release exceeds allocation" warning class. Per-engine
406+
/// `allocated()` saturates to zero on over-release, so this
407+
/// counter is the only post-hoc observable for the bug.
408+
pub fn total_over_release_count(&self) -> usize {
409+
self.budgets.values().map(|b| b.over_release_count()).sum()
410+
}
411+
402412
/// Global utilization as a percentage (0-100).
403413
pub fn global_utilization_percent(&self) -> u8 {
404414
if self.global_ceiling == 0 {

0 commit comments

Comments
 (0)