feat(core): add OwnedCounterGuard for cross-boundary tracking#265
Merged
Conversation
rcoh
requested changes
Apr 10, 2026
| self.0 | ||
| .0 | ||
| .fetch_update( | ||
| std::sync::atomic::Ordering::Relaxed, |
aba6408 to
09ef761
Compare
09ef761 to
c6faa24
Compare
rcoh
approved these changes
Apr 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📬 Issue #, if available:
Closes #264
✍️ Description of changes:
Summary
CounterGuardborrows&'a AtomicU64, which prevents it from being stored in structs that outlive the borrow (e.g. response body wrappers in tower middleware). Users currently fall back to manualfetch_add/fetch_sub, losing RAII safety.This adds
OwnedCounterGuard, which holds anArc<Counter>instead of a reference. It has the sameDropbehavior (saturating decrement) and the sameCloseValueimpls asCounterGuard, but can be freely moved across async boundaries.a📬 Issue #, if available:
Closes #264
✍️ Description of changes:
Summary
CounterGuardborrows&'a AtomicU64, which prevents it from being stored in structs that outlive the borrow (e.g. response body wrappers in tower middleware). Users currently fall back to manualfetch_add/fetch_sub, losing RAII safety.This adds
OwnedCounterGuard, which internally shares the counter and can be freely moved across async boundaries. It has the sameDropbehavior (saturating decrement) and the sameCloseValueimpls asCounterGuard.The guard is
Clone. Cloning shares the underlying counter without incrementing it; each clone independently decrements on drop. Callers are responsible for ensuring the counter has been incremented a corresponding number of times.The constructor is
Counter::increment_scoped_owned(self: &Arc<Self>), mirroring the existingCounter::increment_scoped(&self)API.OwnedCounterGuardis re-exported from bothmetrique-coreand the top-levelmetriquecrate.Testing
Unit tests covering increment/drop semantics, saturation at zero,
CloseValuebehavior, cross-thread movement, and multiple concurrent guards on the same counter.🔏 By submitting this pull request
The constructor is
Counter::increment_scoped_owned(self: &Arc<Self>), mirroring the existingCounter::increment_scoped(&self)API. The caller wraps theirCounterin anArc, which is the natural choice for shared state in async contexts.OwnedCounterGuardis re-exported from bothmetrique-coreand the top-levelmetriquecrate.Testing
Unit tests covering increment/drop semantics, saturation at zero,
CloseValuebehavior, cross-thread movement, and multiple concurrent guards on the same counter.🔏 By submitting this pull request