Skip to content

Commit

Permalink
feat(metrics/family): add Family::get_or_create_clone()
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
cratelyn committed Nov 18, 2024
1 parent 12923ca commit 7216218
Showing 1 changed file with 50 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/metrics/family.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,37 @@ impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
}
}

impl<S: Clone + std::hash::Hash + Eq, M: Clone, C: MetricConstructor<M>> Family<S, M, C>
where
S: Clone + std::hash::Hash + Eq,
M: Clone,
C: MetricConstructor<M>,
{
/// 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::<Vec<(String, String)>, 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<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
/// Access a metric with the given label set, creating it if one does not
/// yet exist.
Expand Down Expand Up @@ -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);
}
}

0 comments on commit 7216218

Please sign in to comment.