1use crate::error::Result;
39use crate::handler::BoxFuture;
40use crate::request::Request;
41use crate::response::Response;
42use std::collections::BTreeMap;
43use std::fmt::Display;
44use std::future::Future;
45use std::sync::Arc;
46use std::time::{Duration, Instant};
47use tokio::sync::Mutex;
48
49type CheckFn = dyn Fn() -> BoxFuture<'static, std::result::Result<(), String>> + Send + Sync;
50
51struct Check {
52 name: String,
53 critical: bool,
54 run: Arc<CheckFn>,
55}
56
57#[derive(Clone)]
58struct Outcome {
59 ok: bool,
60 critical: bool,
61 millis: u64,
62 error: Option<String>,
63}
64
65struct Cached {
66 at: Instant,
67 outcomes: Arc<BTreeMap<String, Outcome>>,
68}
69
70struct Inner {
71 checks: Vec<Check>,
72 timeout: Duration,
73 cache_for: Duration,
74 expose_errors: bool,
75 cache: Mutex<Option<Cached>>,
76}
77
78#[derive(Clone)]
81pub struct Health {
82 inner: Arc<Inner>,
83}
84
85impl Default for Health {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91impl Health {
92 pub fn new() -> Self {
93 Self {
94 inner: Arc::new(Inner {
95 checks: Vec::new(),
96 timeout: Duration::from_secs(2),
97 cache_for: Duration::from_secs(1),
98 expose_errors: false,
99 cache: Mutex::new(None),
100 }),
101 }
102 }
103
104 fn edit(mut self, f: impl FnOnce(&mut Inner)) -> Self {
105 let inner = Arc::get_mut(&mut self.inner).expect("configure Health before sharing it");
106 f(inner);
107 self
108 }
109
110 fn add<F, Fut, E>(self, name: &str, critical: bool, check: F) -> Self
111 where
112 F: Fn() -> Fut + Send + Sync + 'static,
113 Fut: Future<Output = std::result::Result<(), E>> + Send + 'static,
114 E: Display,
115 {
116 let name = name.to_string();
117 self.edit(|i| {
118 i.checks.push(Check {
119 name,
120 critical,
121 run: Arc::new(move || {
122 let fut = check();
123 Box::pin(async move { fut.await.map_err(|e| e.to_string()) })
124 }),
125 })
126 })
127 }
128
129 pub fn check<F, Fut, E>(self, name: &str, check: F) -> Self
131 where
132 F: Fn() -> Fut + Send + Sync + 'static,
133 Fut: Future<Output = std::result::Result<(), E>> + Send + 'static,
134 E: Display,
135 {
136 self.add(name, true, check)
137 }
138
139 pub fn optional<F, Fut, E>(self, name: &str, check: F) -> Self
142 where
143 F: Fn() -> Fut + Send + Sync + 'static,
144 Fut: Future<Output = std::result::Result<(), E>> + Send + 'static,
145 E: Display,
146 {
147 self.add(name, false, check)
148 }
149
150 pub fn timeout(self, timeout: Duration) -> Self {
152 self.edit(|i| i.timeout = timeout)
153 }
154
155 pub fn cache_for(self, ttl: Duration) -> Self {
158 self.edit(|i| i.cache_for = ttl)
159 }
160
161 pub fn expose_errors(self) -> Self {
164 self.edit(|i| i.expose_errors = true)
165 }
166
167 pub fn liveness(
169 &self,
170 ) -> impl Fn(Request) -> BoxFuture<'static, Result<Response>> + Send + Sync + Clone + 'static
171 {
172 |_req| {
173 Box::pin(async {
174 Ok(Response::json(serde_json::json!({ "status": "up" }))
175 .with_header("cache-control", "no-store"))
176 })
177 }
178 }
179
180 pub fn readiness(
182 &self,
183 ) -> impl Fn(Request) -> BoxFuture<'static, Result<Response>> + Send + Sync + Clone + 'static
184 {
185 let health = self.clone();
186 move |req: Request| {
187 let health = health.clone();
188 Box::pin(async move { Ok(health.report(&req).await) })
189 }
190 }
191
192 async fn outcomes(&self) -> Arc<BTreeMap<String, Outcome>> {
193 let inner = &self.inner;
194 let mut cache = inner.cache.lock().await;
196 if let Some(c) = cache.as_ref() {
197 if c.at.elapsed() < inner.cache_for {
198 return c.outcomes.clone();
199 }
200 }
201 let runs = inner.checks.iter().map(|check| async move {
202 let started = Instant::now();
203 let result = tokio::time::timeout(inner.timeout, (check.run)()).await;
204 let (ok, error) = match result {
205 Ok(Ok(())) => (true, None),
206 Ok(Err(e)) => (false, Some(e)),
207 Err(_) => (false, Some(format!("timed out after {:?}", inner.timeout))),
208 };
209 (
210 check.name.clone(),
211 Outcome {
212 ok,
213 critical: check.critical,
214 millis: started.elapsed().as_millis() as u64,
215 error,
216 },
217 )
218 });
219 let outcomes: BTreeMap<_, _> = join_all(runs).await.into_iter().collect();
220 let outcomes = Arc::new(outcomes);
221 *cache = Some(Cached {
222 at: Instant::now(),
223 outcomes: outcomes.clone(),
224 });
225 outcomes
226 }
227
228 async fn report(&self, req: &Request) -> Response {
229 let no_store = |r: Response| r.with_header("cache-control", "no-store");
230 if req.shutdown_signal().is_draining() {
231 return no_store(
232 Response::json(serde_json::json!({ "status": "draining" }))
233 .with_status(http::StatusCode::SERVICE_UNAVAILABLE),
234 );
235 }
236 let outcomes = self.outcomes().await;
237 let down = outcomes.values().any(|o| !o.ok && o.critical);
238 let degraded = outcomes.values().any(|o| !o.ok && !o.critical);
239 let status = if down {
240 "down"
241 } else if degraded {
242 "degraded"
243 } else {
244 "up"
245 };
246 let checks: serde_json::Map<_, _> = outcomes
247 .iter()
248 .map(|(name, o)| {
249 let mut entry = serde_json::json!({
250 "status": if o.ok { "up" } else { "down" },
251 "critical": o.critical,
252 "ms": o.millis,
253 });
254 if let (false, Some(err), true) = (o.ok, &o.error, self.inner.expose_errors) {
255 entry["error"] = err.clone().into();
256 }
257 (name.clone(), entry)
258 })
259 .collect();
260 let code = if down {
261 http::StatusCode::SERVICE_UNAVAILABLE
262 } else {
263 http::StatusCode::OK
264 };
265 no_store(
266 Response::json(serde_json::json!({ "status": status, "checks": checks }))
267 .with_status(code),
268 )
269 }
270}
271
272async fn join_all<F: Future>(futures: impl IntoIterator<Item = F>) -> Vec<F::Output> {
274 let mut futures: Vec<_> = futures.into_iter().map(Box::pin).collect();
275 let mut done: Vec<Option<F::Output>> = futures.iter().map(|_| None).collect();
276 std::future::poll_fn(|cx| {
277 let mut pending = false;
278 for (i, f) in futures.iter_mut().enumerate() {
279 if done[i].is_none() {
280 match f.as_mut().poll(cx) {
281 std::task::Poll::Ready(v) => done[i] = Some(v),
282 std::task::Poll::Pending => pending = true,
283 }
284 }
285 }
286 if pending {
287 std::task::Poll::Pending
288 } else {
289 std::task::Poll::Ready(())
290 }
291 })
292 .await;
293 done.into_iter().flatten().collect()
294}