Skip to content

Commit 13dbb03

Browse files
committed
feat(metrics/family): add Family::get_or_create_clone()
fixes #231. this introduces a new method to `Family<S, M, C>` 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<M>` 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 <[email protected]>
1 parent 12923ca commit 13dbb03

File tree

1 file changed

+57
-0
lines changed

1 file changed

+57
-0
lines changed

src/metrics/family.rs

+57
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,40 @@ impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
207207
}
208208
}
209209

210+
impl<S: Clone + std::hash::Hash + Eq, M: Clone, C: MetricConstructor<M>> Family<S, M, C>
211+
where
212+
S: Clone + std::hash::Hash + Eq,
213+
M: Clone,
214+
C: MetricConstructor<M>,
215+
{
216+
/// Access a metric with the given label set, creating it if one does not yet exist.
217+
///
218+
/// ```
219+
/// # use prometheus_client::metrics::counter::{Atomic, Counter};
220+
/// # use prometheus_client::metrics::family::Family;
221+
/// #
222+
/// let family = Family::<Vec<(String, String)>, Counter>::default();
223+
///
224+
/// // Will create and return the metric with label `method="GET"` when first called.
225+
/// family.get_or_create_clone(&vec![("method".to_owned(), "GET".to_owned())]).inc();
226+
///
227+
/// // Will return a clone of the existing metric on all subsequent calls.
228+
/// family.get_or_create_clone(&vec![("method".to_owned(), "GET".to_owned())]).inc();
229+
/// ```
230+
///
231+
/// Callers wishing to avoid a clone of the metric `M` can call
232+
/// [`MetricConstructor::get_or_create()`] to return a reference to the metric instead.
233+
pub fn get_or_create_clone(&self, label_set: &S) -> M {
234+
use std::ops::Deref;
235+
236+
let guard = self.get_or_create(label_set);
237+
let metric = guard.deref().to_owned();
238+
drop(guard);
239+
240+
metric
241+
}
242+
}
243+
210244
impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
211245
/// Access a metric with the given label set, creating it if one does not
212246
/// yet exist.
@@ -225,6 +259,10 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
225259
/// // calls.
226260
/// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
227261
/// ```
262+
///
263+
/// NB: This method can cause deadlocks if multiple metrics within this family are read at
264+
/// once. Use [`MetricConstructor::get_or_create()`] if you would like to avoid this by
265+
/// cloning the metric `M`.
228266
pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<M> {
229267
if let Ok(metric) =
230268
RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set))
@@ -452,4 +490,23 @@ mod tests {
452490
.get()
453491
);
454492
}
493+
494+
/// Tests that [`Family::get_or_create_clone()`] does not cause deadlocks.
495+
#[test]
496+
fn counter_family_does_not_deadlock() {
497+
/// A structure we'll place two counters into, within a single expression.
498+
struct S {
499+
apples: Counter,
500+
oranges: Counter,
501+
}
502+
503+
let family = Family::<(&str, &str), Counter>::default();
504+
let s = S {
505+
apples: family.get_or_create_clone(&("kind", "apple")),
506+
oranges: family.get_or_create_clone(&("kind", "orange")),
507+
};
508+
509+
s.apples.inc();
510+
s.oranges.inc_by(2);
511+
}
455512
}

0 commit comments

Comments
 (0)