From 7216218b9a54ceb0e0f1d8841c94a23604069243 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Fri, 1 Nov 2024 00:00:00 +0000 Subject: [PATCH] feat(metrics/family): add `Family::get_or_create_clone()` fixes #231. this introduces a new method to `Family` to help alleviate the risk of deadlocks when accessing multiple series within a given metrics family. this returns a plain `M`, rather than the `MappedRwLockReadGuard` RAII guard returned by `get_or_create()`. a test case is introduced in this commit to demonstrate that structures accessing multiple series within a single expression will not accidentally create a deadlock. Signed-off-by: katelyn martin --- src/metrics/family.rs | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/metrics/family.rs b/src/metrics/family.rs index 2f23b198..86f271a3 100644 --- a/src/metrics/family.rs +++ b/src/metrics/family.rs @@ -207,6 +207,37 @@ impl Family { } } +impl> Family +where + S: Clone + std::hash::Hash + Eq, + M: Clone, + C: MetricConstructor, +{ + /// Access a metric with the given label set, creating it if one does not yet exist. + /// + /// ``` + /// # use prometheus_client::metrics::counter::{Atomic, Counter}; + /// # use prometheus_client::metrics::family::Family; + /// # + /// let family = Family::, Counter>::default(); + /// + /// // Will create and return the metric with label `method="GET"` when first called. + /// family.get_or_create_clone(&vec![("method".to_owned(), "GET".to_owned())]).inc(); + /// + /// // Will return a clone of the existing metric on all subsequent calls. + /// family.get_or_create_clone(&vec![("method".to_owned(), "GET".to_owned())]).inc(); + /// ``` + pub fn get_or_create_clone(&self, label_set: &S) -> M { + use std::ops::Deref; + + let guard = self.get_or_create(label_set); + let metric = guard.deref().to_owned(); + drop(guard); + + metric + } +} + impl> Family { /// Access a metric with the given label set, creating it if one does not /// yet exist. @@ -452,4 +483,23 @@ mod tests { .get() ); } + + /// Tests that [`Family::get_or_create_clone()`] does not cause deadlocks. + #[test] + fn counter_family_does_not_deadlock() { + /// A structure we'll place two counters into, within a single expression. + struct S { + apples: Counter, + oranges: Counter, + } + + let family = Family::<(&str, &str), Counter>::default(); + let s = S { + apples: family.get_or_create_clone(&("kind", "apple")), + oranges: family.get_or_create_clone(&("kind", "orange")), + }; + + s.apples.inc(); + s.oranges.inc_by(2); + } }