Skip to content

Commit 6b2b0bd

Browse files
committed
fix: propagate locator cache owner panics (PR #419)
1 parent e9e3a70 commit 6b2b0bd

1 file changed

Lines changed: 51 additions & 4 deletions

File tree

crates/pet-core/src/cache.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ pub struct LocatorCache<K, V> {
2626
}
2727

2828
struct InFlightEntry<V> {
29-
result: Mutex<Option<Option<V>>>,
29+
result: Mutex<Option<InFlightResult<V>>>,
3030
changed: Condvar,
3131
}
3232

33+
#[derive(Clone)]
34+
enum InFlightResult<V> {
35+
Value(Option<V>),
36+
Panicked,
37+
}
38+
3339
struct InFlightOwnerGuard<'a, K: Eq + Hash, V> {
3440
key: Option<K>,
3541
entry: Arc<InFlightEntry<V>>,
@@ -56,6 +62,14 @@ impl<K: Eq + Hash, V> InFlightOwnerGuard<'_, K, V> {
5662
}
5763

5864
fn publish_result(&mut self, result: Option<V>) {
65+
self.publish(InFlightResult::Value(result));
66+
}
67+
68+
fn publish_panic(&mut self) {
69+
self.publish(InFlightResult::Panicked);
70+
}
71+
72+
fn publish(&mut self, result: InFlightResult<V>) {
5973
*self
6074
.entry
6175
.result
@@ -76,7 +90,7 @@ impl<K: Eq + Hash, V> InFlightOwnerGuard<'_, K, V> {
7690
impl<K: Eq + Hash, V> Drop for InFlightOwnerGuard<'_, K, V> {
7791
fn drop(&mut self) {
7892
if self.key.is_some() {
79-
self.publish_result(None);
93+
self.publish_panic();
8094
}
8195
}
8296
}
@@ -137,6 +151,11 @@ impl<K: Eq + Hash, V: Clone> LocatorCache<K, V> {
137151
/// running duplicate closures with duplicate side effects. `None` results
138152
/// are shared with current waiters but are not stored in the cache, so later
139153
/// calls can retry the computation.
154+
///
155+
/// The closure must not call `get_or_insert_with` for the same cache and key,
156+
/// directly or indirectly, because the owner would wait on its own in-flight
157+
/// entry. If the owner panics before publishing a result, waiters for the same
158+
/// key are woken and panic instead of silently receiving an uncached `None`.
140159
#[must_use]
141160
pub fn get_or_insert_with<F>(&self, key: K, f: F) -> Option<V>
142161
where
@@ -220,7 +239,12 @@ impl<K: Eq + Hash, V: Clone> LocatorCache<K, V> {
220239
.unwrap_or_else(|poisoned| poisoned.into_inner());
221240
}
222241

223-
result.clone().unwrap()
242+
match result.clone().unwrap() {
243+
InFlightResult::Value(value) => value,
244+
InFlightResult::Panicked => {
245+
panic!("locator cache in-flight owner panicked before publishing a result");
246+
}
247+
}
224248
}
225249

226250
/// Clears all entries from the cache.
@@ -381,7 +405,8 @@ mod tests {
381405
*entry
382406
.result
383407
.lock()
384-
.expect("locator cache in-flight result lock poisoned") = Some(None);
408+
.expect("locator cache in-flight result lock poisoned") =
409+
Some(InFlightResult::Value(None));
385410
entry.changed.notify_all();
386411

387412
assert_eq!(waiter.join().unwrap(), None);
@@ -414,6 +439,28 @@ mod tests {
414439
);
415440
}
416441

442+
#[test]
443+
fn test_cache_panicked_owner_wakes_waiters_with_panic() {
444+
let key = "key".to_string();
445+
let entry = Arc::new(InFlightEntry::new());
446+
let in_flight: Mutex<HashMap<String, Arc<InFlightEntry<i32>>>> = Mutex::new(HashMap::new());
447+
448+
in_flight.lock().unwrap().insert(key.clone(), entry.clone());
449+
let owner = InFlightOwnerGuard {
450+
key: Some(key),
451+
entry: entry.clone(),
452+
in_flight: &in_flight,
453+
};
454+
455+
drop(owner);
456+
457+
let waiter_result =
458+
std::panic::catch_unwind(|| LocatorCache::<String, i32>::wait_for_in_flight(entry));
459+
460+
assert!(waiter_result.is_err());
461+
assert!(in_flight.lock().unwrap().is_empty());
462+
}
463+
417464
#[test]
418465
fn test_cache_publish_result_recovers_poisoned_in_flight_locks() {
419466
let key = "key".to_string();

0 commit comments

Comments
 (0)