diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e3205..c798c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `EncodeLabelSet` is now implemented for tuples `(A: EncodeLabelSet, B: EncodeLabelSet)`. + See [PR 257]. + +- `Family::get_or_create_owned` can access a metric in a labeled family. This + method avoids the risk of runtime deadlocks at the expense of creating an + owned type. See [PR 244]. + +[PR 244]: https://github.com/prometheus/client_rust/pull/244 +[PR 257]: https://github.com/prometheus/client_rust/pull/257 ### Changed diff --git a/src/metrics/family.rs b/src/metrics/family.rs index 1a76cf8..d3d55c6 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_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc(); + /// + /// // Will return a clone of the existing metric on all subsequent calls. + /// family.get_or_create_owned(&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_owned(&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 Some(metric) = self.get(label_set) { return metric; @@ -510,4 +548,23 @@ mod tests { let non_existent_string = string_family.get(&"non_existent".to_string()); assert!(non_existent_string.is_none()); } + + /// Tests that [`Family::get_or_create_owned()`] 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_owned(&("kind", "apple")), + oranges: family.get_or_create_owned(&("kind", "orange")), + }; + + s.apples.inc(); + s.oranges.inc_by(2); + } }