@@ -82,8 +82,8 @@ impl CachedFileReader {
8282 }
8383
8484 let load_lock = self . cache . block_load_lock ( & key) . await ;
85- let load_guard = load_lock. lock ( ) . await ;
86- let _shared_guard = self . cache . shared_load_lock ( & key) . await ;
85+ let load_guard = load_lock. clone ( ) . lock_owned ( ) . await ;
86+ let shared_guard = self . cache . shared_load_lock ( & key) . await ;
8787 let result = if let Some ( payload) = self
8888 . cache
8989 . get_block ( & key, expected_len, & self . read_token )
@@ -107,6 +107,20 @@ impl CachedFileReader {
107107 } else {
108108 match self . delegate . read ( start..end) . await {
109109 Ok ( payload) if payload. len ( ) == expected_len => {
110+ if let Some ( slot) = self . cache . background_fill_slot ( ) {
111+ let cache = self . cache . clone ( ) ;
112+ let token = self . read_token . clone ( ) ;
113+ let fill_payload = payload. clone ( ) ;
114+ // Hold download guards until publication completes.
115+ tokio:: spawn ( async move {
116+ let _slot = slot;
117+ let _shared_guard = shared_guard;
118+ cache. put_block ( & key, fill_payload, & token) . await ;
119+ drop ( load_guard) ;
120+ cache. release_block_load_lock ( & key, & load_lock) . await ;
121+ } ) ;
122+ return Ok ( payload) ;
123+ }
110124 self . cache
111125 . put_block ( & key, payload. clone ( ) , & self . read_token )
112126 . await ;
@@ -125,6 +139,7 @@ impl CachedFileReader {
125139 Err ( error) => Err ( error) ,
126140 }
127141 } ;
142+ drop ( shared_guard) ;
128143 drop ( load_guard) ;
129144 self . cache . release_block_load_lock ( & key, & load_lock) . await ;
130145 result
@@ -356,6 +371,104 @@ mod tests {
356371 }
357372 }
358373
374+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
375+ async fn shared_fill_returns_before_publication_and_preserves_single_flight ( ) {
376+ let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
377+ let delegate = Arc :: new ( CountingReader {
378+ data : Bytes :: from_static ( b"abcdefgh" ) ,
379+ reads : AtomicUsize :: new ( 0 ) ,
380+ } ) ;
381+ let cache = Arc :: new (
382+ LocalCache :: new ( LocalCacheConfig {
383+ shared : true ,
384+ dir : Some ( directory. path ( ) . to_path_buf ( ) ) ,
385+ namespace : "test" . into ( ) ,
386+ max_size : Some ( 4096 ) ,
387+ block_size : 4 ,
388+ whitelist : std:: collections:: HashSet :: from ( [ FileType :: Meta ] ) ,
389+ } )
390+ . unwrap ( ) ,
391+ ) ;
392+ let reader = CachedFileReader :: new ( delegate. clone ( ) , "data.parquet" , 8 , cache. clone ( ) ) ;
393+ let gate = std:: fs:: OpenOptions :: new ( )
394+ . read ( true )
395+ . write ( true )
396+ . open (
397+ directory
398+ . path ( )
399+ . join ( "paimon-shared-local-cache-v1/directory.lock" ) ,
400+ )
401+ . unwrap ( ) ;
402+ gate. lock ( ) . unwrap ( ) ;
403+ assert_eq ! (
404+ tokio:: time:: timeout( std:: time:: Duration :: from_millis( 500 ) , reader. read( 0 ..4 ) )
405+ . await
406+ . unwrap( )
407+ . unwrap( ) ,
408+ "abcd"
409+ ) ;
410+ assert_eq ! ( cache. fill_slots. available_permits( ) , 7 ) ;
411+ assert ! (
412+ tokio:: time:: timeout( std:: time:: Duration :: from_millis( 50 ) , reader. read( 0 ..4 ) )
413+ . await
414+ . is_err( )
415+ ) ;
416+ assert_eq ! ( delegate. reads. load( Ordering :: SeqCst ) , 1 ) ;
417+ drop ( gate) ;
418+ assert_eq ! ( reader. read( 0 ..4 ) . await . unwrap( ) , "abcd" ) ;
419+ assert_eq ! ( delegate. reads. load( Ordering :: SeqCst ) , 1 ) ;
420+ let permits = cache
421+ . fill_slots
422+ . clone ( )
423+ . acquire_many_owned ( 8 )
424+ . await
425+ . unwrap ( ) ;
426+ assert ! ( cache. background_fill_slot( ) . is_none( ) ) ;
427+ drop ( permits) ;
428+ }
429+
430+ #[ test]
431+ fn shared_fill_does_not_block_reads_on_another_runtime ( ) {
432+ let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
433+ let delegate = Arc :: new ( CountingReader {
434+ data : Bytes :: from_static ( b"abcdefgh" ) ,
435+ reads : AtomicUsize :: new ( 0 ) ,
436+ } ) ;
437+ let cache = Arc :: new (
438+ LocalCache :: new ( LocalCacheConfig {
439+ shared : true ,
440+ dir : Some ( directory. path ( ) . to_path_buf ( ) ) ,
441+ namespace : "test" . into ( ) ,
442+ max_size : Some ( 4096 ) ,
443+ block_size : 4 ,
444+ whitelist : std:: collections:: HashSet :: from ( [ FileType :: Meta ] ) ,
445+ } )
446+ . unwrap ( ) ,
447+ ) ;
448+ let reader = CachedFileReader :: new ( delegate. clone ( ) , "data.parquet" , 8 , cache) ;
449+ let first_runtime = tokio:: runtime:: Builder :: new_current_thread ( )
450+ . enable_all ( )
451+ . build ( )
452+ . unwrap ( ) ;
453+ assert_eq ! ( first_runtime. block_on( reader. read( 0 ..4 ) ) . unwrap( ) , "abcd" ) ;
454+
455+ // Keep the first runtime alive without driving its background tasks.
456+ let second_runtime = tokio:: runtime:: Builder :: new_current_thread ( )
457+ . enable_all ( )
458+ . build ( )
459+ . unwrap ( ) ;
460+ let second = second_runtime. block_on ( async {
461+ tokio:: time:: timeout ( std:: time:: Duration :: from_millis ( 500 ) , reader. read ( 0 ..4 ) ) . await
462+ } ) ;
463+ assert_eq ! (
464+ second
465+ . expect( "read must not wait for an idle runtime" )
466+ . unwrap( ) ,
467+ "abcd"
468+ ) ;
469+ assert_eq ! ( delegate. reads. load( Ordering :: SeqCst ) , 1 ) ;
470+ }
471+
359472 #[ tokio:: test]
360473 async fn test_cached_range_single_flight_reads_cold_block_once ( ) {
361474 let delegate = Arc :: new ( SlowCountingReader {
0 commit comments