diff --git a/src/metrics/family.rs b/src/metrics/family.rs index 2f23b19..85fcf0b 100644 --- a/src/metrics/family.rs +++ b/src/metrics/family.rs @@ -207,6 +207,40 @@ 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(); + /// ``` + /// + /// Callers wishing to avoid a clone of the metric `M` can call [`Family::get_or_create()`] to + /// return a reference to the metric instead. + 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. @@ -225,6 +259,10 @@ impl> Family MappedRwLockReadGuard { if let Ok(metric) = RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set)) @@ -452,4 +490,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); + } }