@@ -940,13 +940,7 @@ impl TierStoreInner {
940940 let lock_ref = self . get_lock_ref ( locking_key. clone ( ) ) ;
941941 let result: io:: Result < Vec < u8 > > = async {
942942 let _guard = lock_ref. lock ( ) . await ;
943- self . recover_key_locked ( & primary_namespace, & secondary_namespace, & key) . await ?;
944- self . reconcile_ephemeral_cache_key_locked (
945- & primary_namespace,
946- & secondary_namespace,
947- & key,
948- )
949- . await ?;
943+ self . prepare_key_locked ( & primary_namespace, & secondary_namespace, & key) . await ?;
950944
951945 if is_ephemeral_cached_key ( & primary_namespace, & secondary_namespace, & key) {
952946 if let Some ( eph_store) = self . ephemeral_store . as_ref ( ) {
@@ -979,19 +973,19 @@ impl TierStoreInner {
979973 Some ( key. as_str ( ) ) ,
980974 "write" ,
981975 ) ?;
982- self . prepare_namespace ( & primary_namespace, & secondary_namespace) . await ?;
976+ self . ensure_namespace_indexed ( & primary_namespace, & secondary_namespace) . await ?;
983977
984978 self . execute_locked_write ( lock_ref, locking_key, version, || async move {
985979 self . write_locked ( & primary_namespace, & secondary_namespace, & key, buf) . await
986980 } )
987981 . await
988982 }
989983
990- /// Writes one key after recovering any pending operation while its per-key lock is held.
984+ /// Prepares and writes one key while its per-key operation lock is held.
991985 async fn write_locked (
992986 & self , primary_namespace : & str , secondary_namespace : & str , key : & str , value : Vec < u8 > ,
993987 ) -> io:: Result < ( ) > {
994- self . recover_key_locked ( primary_namespace, secondary_namespace, key) . await ?;
988+ self . prepare_key_locked ( primary_namespace, secondary_namespace, key) . await ?;
995989 let tier = self . value_tier ( primary_namespace, secondary_namespace, key) ;
996990 let Some ( index) = self . index . as_ref ( ) else {
997991 return self
@@ -1034,19 +1028,19 @@ impl TierStoreInner {
10341028 Some ( key. as_str ( ) ) ,
10351029 "remove" ,
10361030 ) ?;
1037- self . prepare_namespace ( & primary_namespace, & secondary_namespace) . await ?;
1031+ self . ensure_namespace_indexed ( & primary_namespace, & secondary_namespace) . await ?;
10381032
10391033 self . execute_locked_write ( lock_ref, locking_key, version, || async move {
10401034 self . remove_locked ( & primary_namespace, & secondary_namespace, & key, lazy) . await
10411035 } )
10421036 . await
10431037 }
10441038
1045- /// Removes one key after recovering any pending operation while its per-key lock is held.
1039+ /// Prepares and removes one key while its per-key operation lock is held.
10461040 async fn remove_locked (
10471041 & self , primary_namespace : & str , secondary_namespace : & str , key : & str , lazy : bool ,
10481042 ) -> io:: Result < ( ) > {
1049- self . recover_key_locked ( primary_namespace, secondary_namespace, key) . await ?;
1043+ self . prepare_key_locked ( primary_namespace, secondary_namespace, key) . await ?;
10501044 let tier = self . value_tier ( primary_namespace, secondary_namespace, key) ;
10511045 let Some ( index) = self . index . as_ref ( ) else {
10521046 return self
@@ -1220,7 +1214,7 @@ impl TierStoreInner {
12201214 "list" ,
12211215 ) ?;
12221216
1223- self . prepare_namespace ( & primary_namespace, & secondary_namespace) . await ?;
1217+ self . prepare_namespace_for_listing ( & primary_namespace, & secondary_namespace) . await ?;
12241218 if let Some ( index) = self . index . as_ref ( ) {
12251219 return index. list ( & primary_namespace, & secondary_namespace) . await ;
12261220 }
@@ -1281,8 +1275,8 @@ impl TierStoreInner {
12811275 result
12821276 }
12831277
1284- /// Initializes a namespace, recovers pending membership changes, and reconciles cache placement .
1285- async fn prepare_namespace (
1278+ /// Prepares a complete namespace before exposing its index through a listing .
1279+ async fn prepare_namespace_for_listing (
12861280 & self , primary_namespace : & str , secondary_namespace : & str ,
12871281 ) -> io:: Result < ( ) > {
12881282 self . ensure_namespace_indexed ( primary_namespace, secondary_namespace) . await ?;
@@ -1323,13 +1317,7 @@ impl TierStoreInner {
13231317 let lock_ref = self . get_lock_ref ( locking_key. clone ( ) ) ;
13241318 let result: io:: Result < ( ) > = async {
13251319 let _guard = lock_ref. lock ( ) . await ;
1326- self . recover_key_locked ( primary_namespace, secondary_namespace, key) . await ?;
1327- self . reconcile_ephemeral_cache_key_locked (
1328- primary_namespace,
1329- secondary_namespace,
1330- key,
1331- )
1332- . await
1320+ self . prepare_key_locked ( primary_namespace, secondary_namespace, key) . await
13331321 }
13341322 . await ;
13351323 self . clean_locks ( & lock_ref, locking_key) ;
@@ -1406,6 +1394,14 @@ impl TierStoreInner {
14061394 index. mark_cache_ready ( primary_namespace, secondary_namespace, key) . await
14071395 }
14081396
1397+ /// Recovers and reconciles one key while its per-key operation lock is held by the caller.
1398+ async fn prepare_key_locked (
1399+ & self , primary_namespace : & str , secondary_namespace : & str , key : & str ,
1400+ ) -> io:: Result < ( ) > {
1401+ self . recover_key_locked ( primary_namespace, secondary_namespace, key) . await ?;
1402+ self . reconcile_ephemeral_cache_key_locked ( primary_namespace, secondary_namespace, key) . await
1403+ }
1404+
14091405 /// Completes journaled creates and removals before exposing a namespace.
14101406 async fn recover_namespace (
14111407 & self , primary_namespace : & str , secondary_namespace : & str ,
@@ -1495,7 +1491,7 @@ impl TierStoreInner {
14951491 "list_paginated" ,
14961492 ) ?;
14971493
1498- self . prepare_namespace ( & primary_namespace, & secondary_namespace) . await ?;
1494+ self . prepare_namespace_for_listing ( & primary_namespace, & secondary_namespace) . await ?;
14991495 if let Some ( index) = self . index . as_ref ( ) {
15001496 return index
15011497 . list_paginated ( & primary_namespace, & secondary_namespace, page_token)
@@ -1582,7 +1578,7 @@ mod tests {
15821578 use std:: panic:: RefUnwindSafe ;
15831579 use std:: path:: PathBuf ;
15841580 use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
1585- use std:: sync:: { Arc , Mutex } ;
1581+ use std:: sync:: Arc ;
15861582
15871583 use lightning:: util:: logger:: Level ;
15881584 use lightning:: util:: persist:: {
@@ -1593,7 +1589,6 @@ mod tests {
15931589 NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE , SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
15941590 } ;
15951591 use lightning_persister:: fs_store:: v2:: FilesystemStoreV2 ;
1596- use tokio:: sync:: oneshot;
15971592
15981593 use super :: * ;
15991594 use crate :: io:: test_utils:: {
@@ -2144,6 +2139,80 @@ mod tests {
21442139 . unwrap( ) ) ;
21452140 }
21462141
2142+ #[ tokio:: test]
2143+ async fn writing_one_cache_key_only_reconciles_that_key ( ) {
2144+ let base_dir = random_storage_path ( ) ;
2145+ let log_path = base_dir. join ( "tier_store_test.log" ) . to_string_lossy ( ) . into_owned ( ) ;
2146+ let logger = Arc :: new ( Logger :: new_fs_writer ( log_path, Level :: Trace ) . unwrap ( ) ) ;
2147+ let _cleanup = CleanupDir ( base_dir) ;
2148+
2149+ let primary_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
2150+ let ephemeral_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
2151+ for ( key, value) in
2152+ [ ( SCORER_PERSISTENCE_KEY , vec ! [ 1 ] ) , ( EXTERNAL_PATHFINDING_SCORES_CACHE_KEY , vec ! [ 2 ] ) ]
2153+ {
2154+ primary_store
2155+ . write (
2156+ SCORER_PERSISTENCE_PRIMARY_NAMESPACE ,
2157+ SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
2158+ key,
2159+ value,
2160+ )
2161+ . await
2162+ . unwrap ( ) ;
2163+ }
2164+ let mut tier = setup_tier_store ( Arc :: clone ( & primary_store) , logger) ;
2165+ set_test_index_store ( & mut tier) ;
2166+ tier. set_ephemeral_store ( Arc :: clone ( & ephemeral_store) ) ;
2167+
2168+ tier. write (
2169+ SCORER_PERSISTENCE_PRIMARY_NAMESPACE ,
2170+ SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
2171+ SCORER_PERSISTENCE_KEY ,
2172+ vec ! [ 3 ] ,
2173+ )
2174+ . await
2175+ . unwrap ( ) ;
2176+ assert ! ( primary_store
2177+ . read(
2178+ SCORER_PERSISTENCE_PRIMARY_NAMESPACE ,
2179+ SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
2180+ SCORER_PERSISTENCE_KEY ,
2181+ )
2182+ . await
2183+ . is_err( ) ) ;
2184+ assert_eq ! (
2185+ ephemeral_store
2186+ . read(
2187+ SCORER_PERSISTENCE_PRIMARY_NAMESPACE ,
2188+ SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
2189+ SCORER_PERSISTENCE_KEY ,
2190+ )
2191+ . await
2192+ . unwrap( ) ,
2193+ vec![ 3 ]
2194+ ) ;
2195+ assert ! ( ephemeral_store
2196+ . read(
2197+ SCORER_PERSISTENCE_PRIMARY_NAMESPACE ,
2198+ SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
2199+ EXTERNAL_PATHFINDING_SCORES_CACHE_KEY ,
2200+ )
2201+ . await
2202+ . is_err( ) ) ;
2203+ assert_eq ! (
2204+ primary_store
2205+ . read(
2206+ SCORER_PERSISTENCE_PRIMARY_NAMESPACE ,
2207+ SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
2208+ EXTERNAL_PATHFINDING_SCORES_CACHE_KEY ,
2209+ )
2210+ . await
2211+ . unwrap( ) ,
2212+ vec![ 2 ]
2213+ ) ;
2214+ }
2215+
21472216 #[ tokio:: test]
21482217 async fn namespace_initialization_preserves_existing_primary_order_across_pages ( ) {
21492218 let base_dir = random_storage_path ( ) ;
@@ -2336,6 +2405,40 @@ mod tests {
23362405 assert ! ( tier. read( "namespace" , "" , "stuck" ) . await . is_err( ) ) ;
23372406 }
23382407
2408+ #[ tokio:: test]
2409+ async fn writes_and_removals_recover_only_the_requested_key ( ) {
2410+ let base_dir = random_storage_path ( ) ;
2411+ let log_path = base_dir. join ( "tier_store_test.log" ) . to_string_lossy ( ) . into_owned ( ) ;
2412+ let logger = Arc :: new ( Logger :: new_fs_writer ( log_path, Level :: Trace ) . unwrap ( ) ) ;
2413+ let _cleanup = CleanupDir ( base_dir) ;
2414+
2415+ let primary_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
2416+ for ( key, value) in [ ( "stuck" , vec ! [ 1 ] ) , ( "writable" , vec ! [ 2 ] ) , ( "removable" , vec ! [ 3 ] ) ] {
2417+ primary_store. write ( "namespace" , "" , key, value) . await . unwrap ( ) ;
2418+ }
2419+ let mut tier = setup_tier_store ( Arc :: clone ( & primary_store) , logger) ;
2420+ set_test_index_store ( & mut tier) ;
2421+ tier. inner . ensure_namespace_indexed ( "namespace" , "" ) . await . unwrap ( ) ;
2422+ let pending = JournalEntry {
2423+ primary_namespace : "namespace" . to_string ( ) ,
2424+ secondary_namespace : String :: new ( ) ,
2425+ key : "stuck" . to_string ( ) ,
2426+ tier : ValueTier :: Primary ,
2427+ requires_backup : true ,
2428+ operation : JournalOperation :: Remove { lazy : false } ,
2429+ } ;
2430+ let index = tier. inner . index . as_ref ( ) . unwrap ( ) ;
2431+ index. write_journal_entry ( & pending) . await . unwrap ( ) ;
2432+
2433+ tier. write ( "namespace" , "" , "writable" , vec ! [ 4 ] ) . await . unwrap ( ) ;
2434+ tier. remove ( "namespace" , "" , "removable" , false ) . await . unwrap ( ) ;
2435+
2436+ assert_eq ! ( primary_store. read( "namespace" , "" , "writable" ) . await . unwrap( ) , vec![ 4 ] ) ;
2437+ assert ! ( primary_store. read( "namespace" , "" , "removable" ) . await . is_err( ) ) ;
2438+ assert ! ( index. read_journal_entry( "namespace" , "" , "stuck" ) . await . is_ok( ) ) ;
2439+ assert ! ( KVStore :: list( & tier, "namespace" , "" ) . await . is_err( ) ) ;
2440+ }
2441+
23392442 #[ tokio:: test]
23402443 async fn read_recovers_pending_removal_before_returning_value ( ) {
23412444 let base_dir = random_storage_path ( ) ;
@@ -2478,34 +2581,18 @@ mod tests {
24782581 }
24792582
24802583 #[ tokio:: test]
2481- async fn queued_write_recovers_pending_create_under_key_lock_before_classifying ( ) {
2584+ async fn write_recovers_pending_create_under_key_lock_before_classifying ( ) {
24822585 let base_dir = random_storage_path ( ) ;
24832586 let log_path = base_dir. join ( "tier_store_test.log" ) . to_string_lossy ( ) . into_owned ( ) ;
24842587 let logger = Arc :: new ( Logger :: new_fs_writer ( log_path, Level :: Trace ) . unwrap ( ) ) ;
24852588 let _cleanup = CleanupDir ( base_dir) ;
24862589
24872590 let primary_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
2488- let ( snapshot_taken_tx, snapshot_taken_rx) = oneshot:: channel ( ) ;
2489- let ( allow_return_tx, allow_return_rx) = oneshot:: channel ( ) ;
2490- let index_store: Arc < DynStore > =
2491- Arc :: new ( DynStoreWrapper ( InstrumentedStore :: new ( StoreBehavior :: GateJournalList {
2492- snapshot_taken : Mutex :: new ( Some ( snapshot_taken_tx) ) ,
2493- allow_return : Mutex :: new ( Some ( allow_return_rx) ) ,
2494- } ) ) ) ;
2591+ let index_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
24952592 let mut tier = setup_tier_store ( Arc :: clone ( & primary_store) , logger) ;
24962593 tier. set_index_store ( TierStoreIndex :: from_store ( index_store) ) ;
24972594 tier. inner . ensure_namespace_indexed ( "namespace" , "" ) . await . unwrap ( ) ;
24982595
2499- let tier = Arc :: new ( tier) ;
2500- let locking_key = tier. inner . build_locking_key ( "namespace" , "" , "key" ) ;
2501- let lock_ref = tier. inner . get_lock_ref ( locking_key) ;
2502- let guard = lock_ref. lock ( ) . await ;
2503- let write_task = {
2504- let tier = Arc :: clone ( & tier) ;
2505- tokio:: spawn ( async move { tier. write ( "namespace" , "" , "key" , vec ! [ 2 ] ) . await } )
2506- } ;
2507- snapshot_taken_rx. await . unwrap ( ) ;
2508-
25092596 let pending = JournalEntry {
25102597 primary_namespace : "namespace" . to_string ( ) ,
25112598 secondary_namespace : String :: new ( ) ,
@@ -2517,11 +2604,8 @@ mod tests {
25172604 let index = tier. inner . index . as_ref ( ) . unwrap ( ) ;
25182605 index. write_journal_entry ( & pending) . await . unwrap ( ) ;
25192606 primary_store. write ( "namespace" , "" , "key" , vec ! [ 1 ] ) . await . unwrap ( ) ;
2520- allow_return_tx. send ( ( ) ) . unwrap ( ) ;
2521- drop ( guard) ;
2522- drop ( lock_ref) ;
25232607
2524- write_task . await . unwrap ( ) . unwrap ( ) ;
2608+ tier . write ( "namespace" , "" , "key" , vec ! [ 2 ] ) . await . unwrap ( ) ;
25252609 assert_eq ! ( primary_store. read( "namespace" , "" , "key" ) . await . unwrap( ) , vec![ 2 ] ) ;
25262610 assert ! ( index. contains_entry( "namespace" , "" , "key" ) . await . unwrap( ) ) ;
25272611 assert ! ( index. read_journal_entry( "namespace" , "" , "key" ) . await . is_err( ) ) ;
@@ -2578,14 +2662,8 @@ mod tests {
25782662
25792663 enum StoreBehavior {
25802664 FailList ,
2581- FailWrite {
2582- attempts : Arc < AtomicUsize > ,
2583- } ,
2665+ FailWrite { attempts : Arc < AtomicUsize > } ,
25842666 FailRemove ,
2585- GateJournalList {
2586- snapshot_taken : Mutex < Option < oneshot:: Sender < ( ) > > > ,
2587- allow_return : Mutex < Option < oneshot:: Receiver < ( ) > > > ,
2588- } ,
25892667 }
25902668
25912669 /// A store that injects selected failures or synchronization points while delegating other
@@ -2648,9 +2726,7 @@ mod tests {
26482726 ) -> impl Future < Output = Result < Vec < String > , io:: Error > > + ' static + Send {
26492727 let list = match & self . behavior {
26502728 StoreBehavior :: FailList => None ,
2651- StoreBehavior :: FailWrite { .. }
2652- | StoreBehavior :: FailRemove
2653- | StoreBehavior :: GateJournalList { .. } => {
2729+ StoreBehavior :: FailWrite { .. } | StoreBehavior :: FailRemove => {
26542730 Some ( KVStore :: list ( & self . inner , primary_namespace, secondary_namespace) )
26552731 } ,
26562732 } ;
@@ -2669,23 +2745,6 @@ mod tests {
26692745 page_token : Option < PageToken > ,
26702746 ) -> impl Future < Output = Result < PaginatedListResponse , io:: Error > > + ' static + Send {
26712747 let fails = matches ! ( & self . behavior, StoreBehavior :: FailList ) ;
2672- let gate = match & self . behavior {
2673- StoreBehavior :: GateJournalList { snapshot_taken, allow_return } => {
2674- if primary_namespace == INDEX_JOURNAL_PRIMARY_NAMESPACE {
2675- let snapshot_taken = snapshot_taken. lock ( ) . unwrap ( ) . take ( ) ;
2676- let allow_return = allow_return. lock ( ) . unwrap ( ) . take ( ) ;
2677- match ( snapshot_taken, allow_return) {
2678- ( Some ( snapshot_taken) , Some ( allow_return) ) => {
2679- Some ( ( snapshot_taken, allow_return) )
2680- } ,
2681- _ => None ,
2682- }
2683- } else {
2684- None
2685- }
2686- } ,
2687- _ => None ,
2688- } ;
26892748 let list = PaginatedKVStore :: list_paginated (
26902749 & self . inner ,
26912750 primary_namespace,
@@ -2696,14 +2755,7 @@ mod tests {
26962755 if fails {
26972756 return Err ( io:: Error :: new ( io:: ErrorKind :: Other , "list_paginated failed" ) ) ;
26982757 }
2699- let response = list. await ?;
2700- if let Some ( ( snapshot_taken, allow_return) ) = gate {
2701- let _ = snapshot_taken. send ( ( ) ) ;
2702- allow_return. await . map_err ( |_| {
2703- io:: Error :: new ( io:: ErrorKind :: Other , "journal-list gate was dropped" )
2704- } ) ?;
2705- } ;
2706- Ok ( response)
2758+ list. await
27072759 }
27082760 }
27092761 }
0 commit comments