@@ -557,20 +557,16 @@ impl LruPrefixCache {
557557
558558 /// Removes a key's entry from the cache without recording a negative result.
559559 /// Used for invalidation before a write, where we don't yet know the outcome.
560- /// Also removes any containing FindKeys/FindKeyValues entries that could
561- /// return stale data for this key.
560+ ///
561+ /// Removes containing `FindKeyValues` entries to prevent stale reads via
562+ /// `query_read_value`. Containing `FindKeys` entries are kept — they are
563+ /// only used for key listing (not value reads) and are needed by
564+ /// `record_key_deletion_unchecked` for surgical updates.
562565 pub ( crate ) fn invalidate_key ( & mut self , key : & [ u8 ] ) {
563566 let cache_key = CacheKey :: Value ( key. to_vec ( ) ) ;
564567 self . remove_cache_key_if_exists ( & cache_key) ;
565568 if self . has_exclusive_access {
566- let find_keys_to_remove = self
567- . get_existing_find_keys_entry ( key)
568- . map ( |( lb, _) | lb. to_vec ( ) ) ;
569- if let Some ( lower_bound) = find_keys_to_remove {
570- let cache_key = CacheKey :: FindKeys ( lower_bound. clone ( ) ) ;
571- self . remove_cache_key_if_exists ( & cache_key) ;
572- self . find_keys_map . remove ( & lower_bound) ;
573- }
569+ // Remove containing FindKeyValues entries to prevent stale value reads.
574570 let find_kv_to_remove = self
575571 . get_existing_find_key_values_entry ( key)
576572 . map ( |( lb, _) | lb. to_vec ( ) ) ;
@@ -619,7 +615,6 @@ impl LruPrefixCache {
619615 }
620616
621617 /// Safely record the deletion of a key from the cache.
622- #[ cfg( test) ]
623618 pub ( crate ) fn delete_key ( & mut self , key : & [ u8 ] ) {
624619 self . invalidate_key ( key) ;
625620 self . record_key_deletion_unchecked ( key) ;
@@ -808,18 +803,10 @@ impl LruPrefixCache {
808803 let cache_key = CacheKey :: FindKeyValues ( prefix) ;
809804 self . remove_cache_key ( & cache_key) ;
810805 }
811- // Remove any containing FindKeys entry. We cannot surgically
812- // delete sub-keys because that would implicitly assert those keys
813- // don't exist — which we don't know yet.
814- let find_keys_to_remove = self
815- . get_existing_find_keys_entry ( key_prefix)
816- . map ( |( lb, _) | lb. to_vec ( ) ) ;
817- if let Some ( lower_bound) = find_keys_to_remove {
818- let cache_key = CacheKey :: FindKeys ( lower_bound. clone ( ) ) ;
819- self . remove_cache_key_if_exists ( & cache_key) ;
820- self . find_keys_map . remove ( & lower_bound) ;
821- }
822- // Same for containing FindKeyValues entries.
806+ // Remove containing FindKeyValues entries to prevent stale value
807+ // reads. Containing FindKeys entries are kept — they are only used
808+ // for key listing and are needed by record_prefix_deletion_unchecked
809+ // for surgical updates.
823810 let find_kv_to_remove = self
824811 . get_existing_find_key_values_entry ( key_prefix)
825812 . map ( |( lb, _) | lb. to_vec ( ) ) ;
@@ -870,8 +857,7 @@ impl LruPrefixCache {
870857 }
871858 }
872859
873- /// Safely record the deletion of a key prefix in the cache.
874- #[ cfg( test) ]
860+ /// Safely record the deletion of a key prefix from the cache.
875861 pub ( crate ) fn delete_prefix ( & mut self , key_prefix : & [ u8 ] ) {
876862 self . invalidate_prefix ( key_prefix) ;
877863 self . record_prefix_deletion_unchecked ( key_prefix) ;
@@ -2674,4 +2660,214 @@ mod tests {
26742660 cache. insert_read_value ( & key, & Some ( value) ) ;
26752661 cache. check_coherence ( ) ;
26762662 }
2663+
2664+ // --- invalidate_key tests ---
2665+
2666+ #[ test]
2667+ fn test_invalidate_key_does_not_cache_negatively ( ) {
2668+ let mut cache = create_test_cache ( true ) ;
2669+ let key = vec ! [ 1 , 2 ] ;
2670+ let value = vec ! [ 42 ] ;
2671+
2672+ // Insert a value, then invalidate it.
2673+ cache. insert_read_value ( & key, & Some ( value) ) ;
2674+ cache. check_coherence ( ) ;
2675+ assert ! ( cache. query_read_value( & key) . is_some( ) ) ;
2676+
2677+ cache. invalidate_key ( & key) ;
2678+ cache. check_coherence ( ) ;
2679+
2680+ // After invalidation, the key should be unknown (None), NOT DoesNotExist.
2681+ // None means "not in cache, ask the DB". Some(None) would mean "known to not exist".
2682+ assert_eq ! ( cache. query_read_value( & key) , None ) ;
2683+ }
2684+
2685+ #[ test]
2686+ fn test_invalidate_key_removes_containing_find_key_values ( ) {
2687+ let mut cache = create_test_cache ( true ) ;
2688+
2689+ // Insert a FindKeyValues entry for a broad prefix that contains our key.
2690+ let prefix = vec ! [ 1 ] ;
2691+ let key = vec ! [ 1 , 2 ] ;
2692+ let value = vec ! [ 42 ] ;
2693+ cache. insert_find_key_values ( prefix. clone ( ) , & [ ( vec ! [ 2 ] , value. clone ( ) ) ] ) ;
2694+ cache. check_coherence ( ) ;
2695+
2696+ // The key should be queryable via the containing entry.
2697+ assert_eq ! ( cache. query_read_value( & key) , Some ( Some ( value) ) ) ;
2698+
2699+ // Invalidate the specific key.
2700+ cache. invalidate_key ( & key) ;
2701+ cache. check_coherence ( ) ;
2702+
2703+ // The containing FindKeyValues entry should be removed, so the key
2704+ // is no longer resolvable from cache.
2705+ assert_eq ! ( cache. query_read_value( & key) , None ) ;
2706+ }
2707+
2708+ #[ test]
2709+ fn test_invalidate_key_on_nonexistent_key_is_noop ( ) {
2710+ let mut cache = create_test_cache ( true ) ;
2711+ let key = vec ! [ 1 , 2 ] ;
2712+
2713+ // Invalidating a key that was never cached should not panic or insert anything.
2714+ cache. invalidate_key ( & key) ;
2715+ cache. check_coherence ( ) ;
2716+ assert_eq ! ( cache. query_read_value( & key) , None ) ;
2717+ }
2718+
2719+ // --- invalidate_prefix tests ---
2720+
2721+ #[ test]
2722+ fn test_invalidate_prefix_does_not_cache_negatively ( ) {
2723+ let mut cache = create_test_cache ( true ) ;
2724+ let key1 = vec ! [ 1 , 2 ] ;
2725+ let key2 = vec ! [ 1 , 3 ] ;
2726+ let value = vec ! [ 42 ] ;
2727+
2728+ cache. insert_read_value ( & key1, & Some ( value. clone ( ) ) ) ;
2729+ cache. insert_read_value ( & key2, & Some ( value. clone ( ) ) ) ;
2730+ cache. check_coherence ( ) ;
2731+
2732+ cache. invalidate_prefix ( & [ 1 ] ) ;
2733+ cache. check_coherence ( ) ;
2734+
2735+ // Keys should be unknown (None), NOT DoesNotExist (Some(None)).
2736+ assert_eq ! ( cache. query_read_value( & key1) , None ) ;
2737+ assert_eq ! ( cache. query_read_value( & key2) , None ) ;
2738+ }
2739+
2740+ #[ test]
2741+ fn test_invalidate_prefix_removes_containing_find_key_values ( ) {
2742+ let mut cache = create_test_cache ( true ) ;
2743+
2744+ // Insert a FindKeyValues entry for prefix [1] containing sub-keys.
2745+ let prefix = vec ! [ 1 ] ;
2746+ let key1 = vec ! [ 1 , 2 , 3 ] ;
2747+ let key2 = vec ! [ 1 , 2 , 4 ] ;
2748+ let value = vec ! [ 42 ] ;
2749+ cache. insert_find_key_values (
2750+ prefix. clone ( ) ,
2751+ & [ ( vec ! [ 2 , 3 ] , value. clone ( ) ) , ( vec ! [ 2 , 4 ] , value. clone ( ) ) ] ,
2752+ ) ;
2753+ cache. check_coherence ( ) ;
2754+
2755+ // Both keys should be queryable via the containing entry.
2756+ assert_eq ! ( cache. query_read_value( & key1) , Some ( Some ( value. clone( ) ) ) ) ;
2757+ assert_eq ! ( cache. query_read_value( & key2) , Some ( Some ( value. clone( ) ) ) ) ;
2758+
2759+ // Invalidate a sub-prefix that covers both keys.
2760+ cache. invalidate_prefix ( & [ 1 , 2 ] ) ;
2761+ cache. check_coherence ( ) ;
2762+
2763+ // The containing entry should be gone — keys are unknown, not negatively cached.
2764+ assert_eq ! ( cache. query_read_value( & key1) , None ) ;
2765+ assert_eq ! ( cache. query_read_value( & key2) , None ) ;
2766+ }
2767+
2768+ #[ test]
2769+ fn test_invalidate_prefix_removes_matching_find_keys ( ) {
2770+ let mut cache = create_test_cache ( true ) ;
2771+
2772+ let prefix = vec ! [ 1 , 2 ] ;
2773+ let keys = vec ! [ vec![ 3 ] , vec![ 4 ] ] ;
2774+ cache. insert_find_keys ( prefix. clone ( ) , & keys) ;
2775+ cache. check_coherence ( ) ;
2776+ assert ! ( cache. query_find_keys( & prefix) . is_some( ) ) ;
2777+
2778+ // Invalidate a broader prefix that covers the FindKeys entry.
2779+ cache. invalidate_prefix ( & [ 1 ] ) ;
2780+ cache. check_coherence ( ) ;
2781+
2782+ // The FindKeys entry should be completely gone.
2783+ assert_eq ! ( cache. query_find_keys( & prefix) , None ) ;
2784+ }
2785+
2786+ // --- delete_prefix regression tests ---
2787+
2788+ #[ test]
2789+ fn test_delete_prefix_records_negative_via_find_key_values ( ) {
2790+ let mut cache = create_test_cache ( true ) ;
2791+ let key1 = vec ! [ 1 , 2 ] ;
2792+ let value = vec ! [ 42 ] ;
2793+
2794+ cache. insert_read_value ( & key1, & Some ( value) ) ;
2795+ cache. check_coherence ( ) ;
2796+
2797+ // delete_prefix should record the deletion so queries return DoesNotExist.
2798+ cache. delete_prefix ( & [ 1 ] ) ;
2799+ cache. check_coherence ( ) ;
2800+
2801+ // Key should be known to not exist (Some(None)), not just unknown (None).
2802+ assert_eq ! ( cache. query_read_value( & key1) , Some ( None ) ) ;
2803+ }
2804+
2805+ #[ test]
2806+ fn test_delete_prefix_updates_containing_find_keys_entry ( ) {
2807+ let mut cache = create_test_cache ( true ) ;
2808+
2809+ // Insert a FindKeys entry with a broader prefix.
2810+ let find_keys_prefix = vec ! [ 1 ] ;
2811+ let keys = vec ! [ vec![ 2 , 3 ] , vec![ 2 , 4 ] , vec![ 3 , 5 ] ] ;
2812+ cache. insert_find_keys ( find_keys_prefix. clone ( ) , & keys) ;
2813+ cache. check_coherence ( ) ;
2814+
2815+ // Delete a sub-prefix.
2816+ cache. delete_prefix ( & [ 1 , 2 ] ) ;
2817+ cache. check_coherence ( ) ;
2818+
2819+ // The containing FindKeys entry should have the matching keys removed.
2820+ let remaining = cache. query_find_keys ( & find_keys_prefix) . unwrap ( ) ;
2821+ assert ! ( !remaining. contains( & vec![ 2 , 3 ] ) ) ;
2822+ assert ! ( !remaining. contains( & vec![ 2 , 4 ] ) ) ;
2823+ assert ! ( remaining. contains( & vec![ 3 , 5 ] ) ) ;
2824+ }
2825+
2826+ // --- Cancellation safety scenario test ---
2827+
2828+ #[ test]
2829+ fn test_invalidate_then_record_does_not_lose_data ( ) {
2830+ // Simulates the write_batch pattern: invalidate before write,
2831+ // record after successful write.
2832+ let mut cache = create_test_cache ( true ) ;
2833+ let key = vec ! [ 1 , 2 ] ;
2834+ let old_value = vec ! [ 10 ] ;
2835+ let new_value = vec ! [ 20 ] ;
2836+
2837+ // Initial state: key has old value.
2838+ cache. insert_read_value ( & key, & Some ( old_value. clone ( ) ) ) ;
2839+ cache. check_coherence ( ) ;
2840+ assert_eq ! ( cache. query_read_value( & key) , Some ( Some ( old_value) ) ) ;
2841+
2842+ // Step 1: invalidate (pre-write).
2843+ cache. invalidate_key ( & key) ;
2844+ cache. check_coherence ( ) ;
2845+ // Key is now unknown — reads would go to DB.
2846+ assert_eq ! ( cache. query_read_value( & key) , None ) ;
2847+
2848+ // Step 2: write succeeds, record new value.
2849+ cache. put_key_value ( & key, & new_value) ;
2850+ cache. check_coherence ( ) ;
2851+ assert_eq ! ( cache. query_read_value( & key) , Some ( Some ( new_value) ) ) ;
2852+ }
2853+
2854+ #[ test]
2855+ fn test_invalidate_without_followup_is_safe ( ) {
2856+ // Simulates cancellation: invalidate runs but the write is cancelled,
2857+ // so no record step happens.
2858+ let mut cache = create_test_cache ( true ) ;
2859+ let key = vec ! [ 1 , 2 ] ;
2860+ let value = vec ! [ 42 ] ;
2861+
2862+ cache. insert_read_value ( & key, & Some ( value) ) ;
2863+ cache. check_coherence ( ) ;
2864+
2865+ // Invalidate (pre-write), then "cancel" — no record step.
2866+ cache. invalidate_key ( & key) ;
2867+ cache. check_coherence ( ) ;
2868+
2869+ // Key is unknown — subsequent reads will go to DB and get correct data.
2870+ // Crucially, it must NOT return the old stale value.
2871+ assert_eq ! ( cache. query_read_value( & key) , None ) ;
2872+ }
26772873}
0 commit comments