@@ -26,10 +26,16 @@ pub struct LocatorCache<K, V> {
2626}
2727
2828struct 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+
3339struct 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> {
7690impl < 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