@@ -283,6 +283,58 @@ where
283283 ret
284284 }
285285 }
286+
287+ /// Similar to [get], query the cache for a given value, but also returns the value even if the
288+ /// value is expired up to `stale_ttl`. If it is a cache miss or the value is stale more than
289+ /// the `stale_ttl`, a lookup will be performed to populate the cache.
290+ pub async fn get_stale (
291+ & self ,
292+ key : & K ,
293+ ttl : Option < Duration > ,
294+ extra : Option < & S > ,
295+ stale_ttl : Duration ,
296+ ) -> ( Result < T , Box < Error > > , CacheStatus ) {
297+ let ( result, cache_status) = self . inner . get_stale ( key) ;
298+ if let Some ( result) = result {
299+ let stale_duration = cache_status. stale ( ) ;
300+ if stale_duration. unwrap_or ( Duration :: ZERO ) <= stale_ttl {
301+ return ( Ok ( result) , cache_status) ;
302+ }
303+ }
304+ let ( res, status) = self . get ( key, ttl, extra) . await ;
305+ ( res, status)
306+ }
307+ }
308+
309+ impl < K , T , CB , S > RTCache < K , T , CB , S >
310+ where
311+ K : Hash + Clone + Send + Sync ,
312+ T : Clone + Send + Sync + ' static ,
313+ S : Clone + Send + Sync ,
314+ CB : Lookup < K , T , S > + Sync + Send ,
315+ {
316+ /// Similar to [get_stale], but when it returns the stale value, it also initiates a lookup
317+ /// in the background in order to refresh the value.
318+ ///
319+ /// Note that this function requires the [RTCache] to be static, which can be done by wrapping
320+ /// it with something like [once_cell::sync::Lazy].
321+ pub async fn get_stale_while_update (
322+ & ' static self ,
323+ key : & K ,
324+ ttl : Option < Duration > ,
325+ extra : Option < & S > ,
326+ stale_ttl : Duration ,
327+ ) -> ( Result < T , Box < Error > > , CacheStatus ) {
328+ let ( result, cache_status) = self . get_stale ( key, ttl, extra, stale_ttl) . await ;
329+ let key = key. clone ( ) ;
330+ let extra = extra. cloned ( ) ;
331+ if cache_status. stale ( ) . is_some ( ) {
332+ tokio:: spawn ( async move {
333+ let _ = self . get ( & key, ttl, extra. as_ref ( ) ) . await ;
334+ } ) ;
335+ }
336+ ( result, cache_status)
337+ }
286338}
287339
288340impl < K , T , CB , S > RTCache < K , T , CB , S >
@@ -686,4 +738,67 @@ mod tests {
686738 . await
687739 . unwrap ( ) ;
688740 }
741+
742+ #[ tokio:: test]
743+ async fn test_get_stale ( ) {
744+ let ttl = Some ( Duration :: from_millis ( 100 ) ) ;
745+ let cache: RTCache < i32 , i32 , TestCB , ExtraOpt > = RTCache :: new ( 10 , None , None ) ;
746+ let opt = Some ( ExtraOpt {
747+ error : false ,
748+ empty : false ,
749+ delay_for : None ,
750+ used : Arc :: new ( AtomicI32 :: new ( 0 ) ) ,
751+ } ) ;
752+ let ( res, hit) = cache. get ( & 1 , ttl, opt. as_ref ( ) ) . await ;
753+ assert_eq ! ( res. unwrap( ) , 1 ) ;
754+ assert_eq ! ( hit, CacheStatus :: Miss ) ;
755+ let ( res, hit) = cache. get ( & 1 , ttl, opt. as_ref ( ) ) . await ;
756+ assert_eq ! ( res. unwrap( ) , 1 ) ;
757+ assert_eq ! ( hit, CacheStatus :: Hit ) ;
758+ tokio:: time:: sleep ( Duration :: from_millis ( 150 ) ) . await ;
759+ let ( res, hit) = cache
760+ . get_stale ( & 1 , ttl, opt. as_ref ( ) , Duration :: from_millis ( 1000 ) )
761+ . await ;
762+ assert_eq ! ( res. unwrap( ) , 1 ) ;
763+ assert ! ( hit. stale( ) . is_some( ) ) ;
764+
765+ let ( res, hit) = cache
766+ . get_stale ( & 1 , ttl, opt. as_ref ( ) , Duration :: from_millis ( 30 ) )
767+ . await ;
768+ assert_eq ! ( res. unwrap( ) , 2 ) ;
769+ assert_eq ! ( hit, CacheStatus :: Expired ) ;
770+ }
771+
772+ #[ tokio:: test]
773+ async fn test_get_stale_while_update ( ) {
774+ use once_cell:: sync:: Lazy ;
775+ let ttl = Some ( Duration :: from_millis ( 100 ) ) ;
776+ static CACHE : Lazy < RTCache < i32 , i32 , TestCB , ExtraOpt > > =
777+ Lazy :: new ( || RTCache :: new ( 10 , None , None ) ) ;
778+ let opt = Some ( ExtraOpt {
779+ error : false ,
780+ empty : false ,
781+ delay_for : None ,
782+ used : Arc :: new ( AtomicI32 :: new ( 0 ) ) ,
783+ } ) ;
784+ let ( res, hit) = CACHE . get ( & 1 , ttl, opt. as_ref ( ) ) . await ;
785+ assert_eq ! ( res. unwrap( ) , 1 ) ;
786+ assert_eq ! ( hit, CacheStatus :: Miss ) ;
787+ let ( res, hit) = CACHE . get ( & 1 , ttl, opt. as_ref ( ) ) . await ;
788+ assert_eq ! ( res. unwrap( ) , 1 ) ;
789+ assert_eq ! ( hit, CacheStatus :: Hit ) ;
790+ tokio:: time:: sleep ( Duration :: from_millis ( 150 ) ) . await ;
791+ let ( res, hit) = CACHE
792+ . get_stale_while_update ( & 1 , ttl, opt. as_ref ( ) , Duration :: from_millis ( 1000 ) )
793+ . await ;
794+ assert_eq ! ( res. unwrap( ) , 1 ) ;
795+ assert ! ( hit. stale( ) . is_some( ) ) ;
796+
797+ // allow the background lookup to finish
798+ tokio:: time:: sleep ( Duration :: from_millis ( 10 ) ) . await ;
799+
800+ let ( res, hit) = CACHE . get ( & 1 , ttl, opt. as_ref ( ) ) . await ;
801+ assert_eq ! ( res. unwrap( ) , 2 ) ;
802+ assert_eq ! ( hit, CacheStatus :: Hit ) ;
803+ }
689804}
0 commit comments