1- use std:: { sync:: Arc , time:: Duration } ;
1+ use std:: { future :: Future , sync:: Arc , time:: Duration } ;
22
33use academy_cache_contracts:: CacheService ;
44use academy_core_health_contracts:: { HealthFeatureService , HealthStatus } ;
@@ -24,17 +24,21 @@ pub struct HealthFeatureServiceImpl<Time, Db, Cache, Email> {
2424
2525#[ derive( Debug , Clone ) ]
2626pub struct HealthFeatureConfig {
27- pub cache_ttl : Duration ,
27+ pub database_cache_ttl : Duration ,
28+ pub cache_cache_ttl : Duration ,
29+ pub email_cache_ttl : Duration ,
2830}
2931
3032#[ derive( Debug , Default ) ]
3133struct State {
32- cache : RwLock < Option < CachedStatus > > ,
34+ database_cache : RwLock < Option < CachedStatusItem > > ,
35+ cache_cache : RwLock < Option < CachedStatusItem > > ,
36+ email_cache : RwLock < Option < CachedStatusItem > > ,
3337}
3438
3539#[ derive( Debug , Clone , Copy ) ]
36- struct CachedStatus {
37- status : HealthStatus ,
40+ struct CachedStatusItem {
41+ status : bool ,
3842 timestamp : DateTime < Utc > ,
3943}
4044
@@ -48,63 +52,98 @@ where
4852{
4953 #[ trace_instrument( skip( self ) ) ]
5054 async fn get_status ( & self ) -> HealthStatus {
55+ let database = self . ping_cached (
56+ "database" ,
57+ & self . state . database_cache ,
58+ self . config . database_cache_ttl ,
59+ || async {
60+ self . db
61+ . ping ( )
62+ . await
63+ . inspect_err ( |err| error ! ( "Failed to ping database: {err}" ) )
64+ . is_ok ( )
65+ } ,
66+ ) ;
67+
68+ let cache = self . ping_cached (
69+ "cache" ,
70+ & self . state . cache_cache ,
71+ self . config . cache_cache_ttl ,
72+ || async {
73+ self . cache
74+ . ping ( )
75+ . await
76+ . inspect_err ( |err| error ! ( "Failed to ping cache: {err}" ) )
77+ . is_ok ( )
78+ } ,
79+ ) ;
80+
81+ let email = self . ping_cached (
82+ "email" ,
83+ & self . state . email_cache ,
84+ self . config . email_cache_ttl ,
85+ || async {
86+ self . email
87+ . ping ( )
88+ . await
89+ . inspect_err ( |err| error ! ( "Failed to ping smtp server: {err}" ) )
90+ . is_ok ( )
91+ } ,
92+ ) ;
93+
94+ let ( database, cache, email) = tokio:: join!( database, cache, email) ;
95+
96+ HealthStatus {
97+ database,
98+ cache,
99+ email,
100+ }
101+ }
102+ }
103+
104+ impl < Time , Db , Cache , Email > HealthFeatureServiceImpl < Time , Db , Cache , Email >
105+ where
106+ Time : TimeService ,
107+ {
108+ #[ trace_instrument( skip( self , f) ) ]
109+ async fn ping_cached < F > (
110+ & self ,
111+ _item : & ' static str ,
112+ cache : & RwLock < Option < CachedStatusItem > > ,
113+ ttl : Duration ,
114+ f : impl FnOnce ( ) -> F ,
115+ ) -> bool
116+ where
117+ F : Future < Output = bool > ,
118+ {
51119 let now = self . time . now ( ) ;
52120
53- let status_if_not_expired = |cached : Option < CachedStatus > | {
54- let CachedStatus { status, timestamp } = cached?;
55- let ttl = timestamp + self . config . cache_ttl - now;
121+ let status_if_not_expired = |cached : Option < CachedStatusItem > | {
122+ let CachedStatusItem { status, timestamp } = cached?;
123+ let ttl = timestamp + ttl - now;
56124 ( ttl > TimeDelta :: zero ( ) )
57125 . then_some ( status)
58126 . inspect ( |_| trace ! ( %ttl, "use cache" ) )
59127 } ;
60128
61- if let Some ( status) = status_if_not_expired ( * self . state . cache . read ( ) . await ) {
129+ if let Some ( status) = status_if_not_expired ( * cache. read ( ) . await ) {
62130 return status;
63131 }
64132
65133 trace ! ( "cache miss, acquire write lock" ) ;
66- let mut cache_guard = self . state . cache . write ( ) . await ;
134+ let mut cache_guard = cache. write ( ) . await ;
67135 if let Some ( status) = status_if_not_expired ( * cache_guard) {
68136 return status;
69137 }
70138
71- let database = async {
72- self . db
73- . ping ( )
74- . await
75- . inspect_err ( |err| error ! ( "Failed to ping database: {err}" ) )
76- . is_ok ( )
77- } ;
78-
79- let cache = async {
80- self . cache
81- . ping ( )
82- . await
83- . inspect_err ( |err| error ! ( "Failed to ping cache: {err}" ) )
84- . is_ok ( )
85- } ;
86-
87- let email = async {
88- self . email
89- . ping ( )
90- . await
91- . inspect_err ( |err| error ! ( "Failed to ping smtp server: {err}" ) )
92- . is_ok ( )
93- } ;
139+ trace ! ( "ping" ) ;
140+ let status = f ( ) . await ;
94141
95- let ( database, cache, email) = tokio:: join!( database, cache, email) ;
96-
97- let status = HealthStatus {
98- database,
99- cache,
100- email,
101- } ;
142+ * cache_guard = Some ( CachedStatusItem {
143+ status,
144+ timestamp : now,
145+ } ) ;
102146
103- cache_guard
104- . insert ( CachedStatus {
105- status,
106- timestamp : now,
107- } )
108- . status
147+ status
109148 }
110149}
0 commit comments