Skip to content

Commit 377ca2d

Browse files
authored
feat(metrics/family): 🍫 add Family::get_or_create_owned() (#244)
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 84e2cc6 commit 377ca2d

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

Diff for: CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
- `EncodeLabelSet` is now implemented for tuples `(A: EncodeLabelSet, B: EncodeLabelSet)`.
12+
See [PR 257].
13+
14+
- `Family::get_or_create_owned` can access a metric in a labeled family. This
15+
method avoids the risk of runtime deadlocks at the expense of creating an
16+
owned type. See [PR 244].
17+
18+
[PR 244]: https://github.com/prometheus/client_rust/pull/244
19+
[PR 257]: https://github.com/prometheus/client_rust/pull/257
1220

1321
### Changed
1422

Diff for: 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_owned(&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_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc();
229+
/// ```
230+
///
231+
/// Callers wishing to avoid a clone of the metric `M` can call [`Family::get_or_create()`] to
232+
/// return a reference to the metric instead.
233+
pub fn get_or_create_owned(&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 [`Family::get_or_create_owned()`] if you would like to avoid this by cloning the
265+
/// metric `M`.
228266
pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<M> {
229267
if let Some(metric) = self.get(label_set) {
230268
return metric;
@@ -510,4 +548,23 @@ mod tests {
510548
let non_existent_string = string_family.get(&"non_existent".to_string());
511549
assert!(non_existent_string.is_none());
512550
}
551+
552+
/// Tests that [`Family::get_or_create_owned()`] does not cause deadlocks.
553+
#[test]
554+
fn counter_family_does_not_deadlock() {
555+
/// A structure we'll place two counters into, within a single expression.
556+
struct S {
557+
apples: Counter,
558+
oranges: Counter,
559+
}
560+
561+
let family = Family::<(&str, &str), Counter>::default();
562+
let s = S {
563+
apples: family.get_or_create_owned(&("kind", "apple")),
564+
oranges: family.get_or_create_owned(&("kind", "orange")),
565+
};
566+
567+
s.apples.inc();
568+
s.oranges.inc_by(2);
569+
}
513570
}

0 commit comments

Comments
 (0)