use std::{fmt::Debug, future::Future}; use serde::{Serialize, de::DeserializeOwned}; use crate::cache::{redis::{FRESH_SECONDS, cache_get, cache_set, now_unix}, refreshing::{REFRESHING, Refreshing}}; pub async fn get_or_compute(key: &str, calculate: F) -> Result where T: Serialize + DeserializeOwned + Send + Sync + 'static + Debug, F: FnOnce() -> Fut + Send + 'static, Fut: Future> + Send + 'static, E: From + std::fmt::Display + Send + 'static, { if let Some(hit) = cache_get(key).await? { let age = now_unix().saturating_sub(hit.fetched_at); if age < FRESH_SECONDS { return Ok(hit.value) }; spawn_refresh(REFRESHING.get().unwrap().clone(), key.to_string(), calculate); return Ok(hit.value); } let value = calculate().await?; cache_set(key, &value).await?; Ok(value) } fn spawn_refresh(refreshing: Refreshing, key: String, calculate: F) where T: Serialize + Send + 'static + Debug + DeserializeOwned + Sync, F: FnOnce() -> Fut + Send + 'static, Fut: Future> + Send + 'static, E: From + std::fmt::Display + Send + 'static, { if !refreshing.lock().unwrap().insert(key.to_string()) { return; } tokio::spawn(async move { match calculate().await { Ok(value) => { if let Err(e) = cache_set(&key, &value).await { eprintln!("Failed to save value {:?} in cache: {:?}", value, e) } } Err(e) => { eprintln!("Failed to refresh value for key {key}: {e}") } } refreshing.lock().unwrap().remove(&key); }); }