Skip to content

Commit e08410a

Browse files
committed
Fix Family::get_or_create
The method should not overwrite an existing key after it obtains the write lock. Consider the following scenario with `Family<LabelSet, Gauge>` used by threads A and B: 1. A and B both call `family.get_or_insert(&label_set)` 2. A and B both acquire read lock and finds no key 3. A gets write lock and inserts a new gauge with value 0 4. A increments returned gauge to 1 5. B gets write lock and inserts a new gauge with value 0 6. B increments returned gauge to 1 7. A decrements gauge back to 0 8. B decrements gauge which now overflows to `u64::MAX` Signed-off-by: Anthony Ramine <[email protected]>
1 parent de81e7b commit e08410a

File tree

2 files changed

+16
-15
lines changed

2 files changed

+16
-15
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818

1919
- Move`Encode` trait from `prometheus_client::encoding::text` to `prometheus_client::encoding`. See [PR 83].
2020

21+
### Fixed
22+
23+
- Fix race condition in `Family::get_or_create`. See [PR 102].
24+
2125
[PR 83]: https://github.com/prometheus/client_rust/pull/83
2226
[PR 85]: https://github.com/prometheus/client_rust/pull/85
2327
[PR 96]: https://github.com/prometheus/client_rust/pull/96
28+
[PR 102]: https://github.com/prometheus/client_rust/pull/102
2429

2530
## [0.18.0]
2631

src/metrics/family.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -216,22 +216,18 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
216216
/// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
217217
/// ```
218218
pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<M> {
219-
if let Ok(metric) =
220-
RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set))
221-
{
222-
return metric;
223-
}
224-
225-
let mut write_guard = self.metrics.write();
226-
write_guard.insert(label_set.clone(), self.constructor.new_metric());
227-
228-
drop(write_guard);
219+
loop {
220+
if let Ok(metric) =
221+
RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set))
222+
{
223+
return metric;
224+
}
229225

230-
RwLockReadGuard::map(self.metrics.read(), |metrics| {
231-
metrics
232-
.get(label_set)
233-
.expect("Metric to exist after creating it.")
234-
})
226+
self.metrics
227+
.write()
228+
.entry(label_set.clone())
229+
.or_insert_with(|| self.constructor.new_metric());
230+
}
235231
}
236232

237233
/// Remove a label set from the metric family.

0 commit comments

Comments
 (0)