mirror of
https://github.com/Xevion/xevion.dev.git
synced 2026-01-31 12:26:39 -06:00
feat: add health checks, OG image generation, and R2 integration
- Implement health check system with caching and singleflight pattern - Add OG image generation via Satori with R2 storage backend - Configure Railway deployment with health check endpoint - Add connection pooling and Unix socket support for Bun SSR - Block external access to internal routes (/internal/*)
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
use futures::future::{BoxFuture, FutureExt, Shared};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// The state of the health check system
|
||||
enum HealthCheckState {
|
||||
/// No check has ever been performed
|
||||
Initial,
|
||||
|
||||
/// A check is currently in progress, all requests await this future
|
||||
Checking {
|
||||
future: Shared<BoxFuture<'static, bool>>,
|
||||
},
|
||||
|
||||
/// We have a cached result from a completed check
|
||||
Cached { healthy: bool, checked_at: Instant },
|
||||
}
|
||||
|
||||
/// Inner state that can be shared across futures
|
||||
struct HealthCheckerInner {
|
||||
state: Mutex<HealthCheckState>,
|
||||
had_success: AtomicBool,
|
||||
}
|
||||
|
||||
/// Manages health check state with caching and singleflight behavior
|
||||
pub struct HealthChecker {
|
||||
inner: Arc<HealthCheckerInner>,
|
||||
check_fn: Arc<dyn Fn() -> BoxFuture<'static, bool> + Send + Sync>,
|
||||
}
|
||||
|
||||
impl HealthChecker {
|
||||
/// Create a new health checker with the given check function
|
||||
pub fn new<F, Fut>(check_fn: F) -> Self
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + 'static,
|
||||
Fut: std::future::Future<Output = bool> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: Arc::new(HealthCheckerInner {
|
||||
state: Mutex::new(HealthCheckState::Initial),
|
||||
had_success: AtomicBool::new(false),
|
||||
}),
|
||||
check_fn: Arc::new(move || check_fn().boxed()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a health check with caching and singleflight behavior
|
||||
pub async fn check(&self) -> bool {
|
||||
let mut state = self.inner.state.lock().await;
|
||||
|
||||
match &*state {
|
||||
HealthCheckState::Initial => {
|
||||
// Start first check, transition to Checking
|
||||
let future = self.create_check_future();
|
||||
*state = HealthCheckState::Checking {
|
||||
future: future.clone(),
|
||||
};
|
||||
drop(state);
|
||||
future.await
|
||||
}
|
||||
HealthCheckState::Checking { future } => {
|
||||
// Join existing check (singleflight)
|
||||
let future = future.clone();
|
||||
drop(state);
|
||||
future.await
|
||||
}
|
||||
HealthCheckState::Cached {
|
||||
healthy,
|
||||
checked_at,
|
||||
} => {
|
||||
// Determine cache window based on startup status
|
||||
let window = if self.inner.had_success.load(Ordering::Relaxed) {
|
||||
Duration::from_secs(15)
|
||||
} else {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
if checked_at.elapsed() < window {
|
||||
// Serve from cache
|
||||
return *healthy;
|
||||
}
|
||||
|
||||
// Cache stale, start new check
|
||||
let future = self.create_check_future();
|
||||
*state = HealthCheckState::Checking {
|
||||
future: future.clone(),
|
||||
};
|
||||
drop(state);
|
||||
future.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a shared future that performs the check and updates state
|
||||
fn create_check_future(&self) -> Shared<BoxFuture<'static, bool>> {
|
||||
let inner = Arc::clone(&self.inner);
|
||||
let check_fn = Arc::clone(&self.check_fn);
|
||||
|
||||
async move {
|
||||
let result = (check_fn)().await;
|
||||
|
||||
// Transition: Checking → Cached
|
||||
*inner.state.lock().await = HealthCheckState::Cached {
|
||||
healthy: result,
|
||||
checked_at: Instant::now(),
|
||||
};
|
||||
|
||||
if result {
|
||||
inner.had_success.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
.boxed()
|
||||
.shared()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user