@@ -29,7 +29,7 @@ pub struct CacheContext {
2929 pub workspace_root : PathBuf ,
3030}
3131
32- #[ derive( Debug ) ]
32+ #[ derive( Clone , Debug ) ]
3333pub struct StorageOptions {
3434 pub only_backends : Vec < Id > ,
3535 pub include_local : bool ,
@@ -115,25 +115,43 @@ impl Storage {
115115 }
116116
117117 pub fn get_backends ( & self ) -> Vec < & BoxedStorageBackend > {
118- let mut backends = vec ! [ ] ;
118+ self . get_backends_with_options ( & self . options )
119+ }
119120
120- if self . options . only_backends . is_empty ( ) {
121- if self . options . include_local {
122- backends. extend ( self . local_backends . iter ( ) ) ;
123- }
121+ pub fn get_backends_with_options ( & self , options : & StorageOptions ) -> Vec < & BoxedStorageBackend > {
122+ let mut backends = vec ! [ ] ;
124123
125- if self . options . include_remote {
126- backends. extend ( self . remote_backends . iter ( ) ) ;
127- }
128- } else {
124+ if options. include_local {
129125 backends. extend ( self . local_backends . iter ( ) ) ;
126+ }
127+
128+ if options. include_remote {
130129 backends. extend ( self . remote_backends . iter ( ) ) ;
131- backends. retain ( |backend| self . options . only_backends . contains ( backend. get_id ( ) ) ) ;
130+ }
131+
132+ if !options. only_backends . is_empty ( ) {
133+ backends. retain ( |backend| options. only_backends . contains ( backend. get_id ( ) ) ) ;
132134 }
133135
134136 backends
135137 }
136138
139+ pub fn get_local_backends ( & self ) -> Vec < & BoxedStorageBackend > {
140+ self . get_backends_with_options ( & StorageOptions {
141+ // Respect previously configured options
142+ include_remote : false ,
143+ ..self . options . clone ( )
144+ } )
145+ }
146+
147+ pub fn get_remote_backends ( & self ) -> Vec < & BoxedStorageBackend > {
148+ self . get_backends_with_options ( & StorageOptions {
149+ // Respect previously configured options
150+ include_local : false ,
151+ ..self . options . clone ( )
152+ } )
153+ }
154+
137155 pub fn is_local_enabled ( & self ) -> bool {
138156 !self . local_backends . is_empty ( )
139157 }
@@ -227,7 +245,7 @@ impl Storage {
227245 continue ;
228246 }
229247
230- background_tasks. push ( tokio:: spawn ( Box :: pin ( archive_manifest_in_backend (
248+ background_tasks. push ( tokio:: spawn ( Box :: pin ( persist_manifest_in_backend (
231249 Arc :: clone ( backend) ,
232250 digest. to_owned ( ) ,
233251 manifest. clone ( ) ,
@@ -254,7 +272,7 @@ impl Storage {
254272 let ManifestSource {
255273 mut manifest,
256274 backend : original_backend,
257- ..
275+ remote ,
258276 } = manifest_source;
259277 let mut backends = VecDeque :: from_iter ( self . get_backends ( ) ) ;
260278 let mut count = 1 ;
@@ -294,6 +312,13 @@ impl Storage {
294312 "Hydrated cache manifest from {count} storage backends"
295313 ) ;
296314
315+ // A remote hit leaves the local tier cold. Warm it from the
316+ // now-in-memory blobs so the next run resolves locally instead of
317+ // round-tripping to the remote again
318+ if remote {
319+ self . warm_local_backends ( digest, & manifest) . await ;
320+ }
321+
297322 return Ok ( Some ( manifest) ) ;
298323 }
299324
@@ -305,6 +330,33 @@ impl Storage {
305330 Ok ( None )
306331 }
307332
333+ /// Warm the local tier after a remote cache hit by persisting the fully
334+ /// hydrated manifest and its blobs into every active, writable local
335+ /// backend, so the next run resolves locally instead of round-tripping to
336+ /// the remote.
337+ async fn warm_local_backends ( & self , digest : & Digest , manifest : & Manifest ) {
338+ let mut background_tasks = self . background_tasks . lock ( ) . unwrap ( ) ;
339+
340+ for backend in self . get_local_backends ( ) {
341+ if !backend. is_writable ( ) {
342+ continue ;
343+ }
344+
345+ trace ! (
346+ storage = backend. get_id( ) . as_str( ) ,
347+ hash = digest. hash. as_str( ) ,
348+ "Warming local storage backend from remote cache hit"
349+ ) ;
350+
351+ background_tasks. push ( tokio:: spawn ( Box :: pin ( persist_manifest_in_backend (
352+ Arc :: clone ( backend) ,
353+ digest. to_owned ( ) ,
354+ manifest. clone ( ) ,
355+ self . context . workspace_root . clone ( ) ,
356+ ) ) ) ) ;
357+ }
358+ }
359+
308360 pub async fn wait_for_background_tasks ( & self ) -> miette:: Result < ( ) > {
309361 let background_tasks = {
310362 self . background_tasks
@@ -360,7 +412,7 @@ impl Storage {
360412 }
361413}
362414
363- async fn archive_manifest_in_backend (
415+ async fn persist_manifest_in_backend (
364416 backend : BoxedStorageBackend ,
365417 digest : Digest ,
366418 mut manifest : Manifest ,
0 commit comments