/
mcmare
/
RustAPI
Обзор
Документация
Войти
/
mcmare
/
RustAPI
Код
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
engine/src/hot_reload.rs
81 строка
3 KB
mcmare
Add hot reload via file watcher; fix cross-cutting middleware to wrap merged router
18 июл 2026, 16:48
18 июл 2026, 16:48
104d8a6
Код
Авторство
О чём код?
use arc_swap::ArcSwap; use notify::{Event, RecursiveMode, Watcher}; use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use tokio::sync::mpsc; use crate::{config, db, dispatch::RouteTable, script::ScriptEngine, DbPool}; /// Watches `config_dir/endpoints` and `config_dir/scripts` for changes. On any change, /// reloads the whole config and — only if it's valid — atomically swaps the live route /// table and clears the script cache. An invalid change (a typo mid-edit, say) is logged /// and the server keeps serving the last-known-good config instead of crashing. /// /// Returns the `notify` watcher, which must be kept alive for as long as reloading should /// keep working (dropping it stops the underlying OS file-watch). pub fn spawn( config_dir: PathBuf, routes: Arc<ArcSwap<RouteTable>>, scripts: Arc<ScriptEngine>, pools: Arc<HashMap<String, DbPool>>, ) -> anyhow::Result<notify::RecommendedWatcher> { let (tx, mut rx) = mpsc::unbounded_channel(); let mut watcher = notify::recommended_watcher(move |res: notify::Result<Event>| { if let Ok(event) = res { let _ = tx.send(event); } })?; let endpoints_dir = config_dir.join("endpoints"); let scripts_dir = config_dir.join("scripts"); if endpoints_dir.exists() { watcher.watch(&endpoints_dir, RecursiveMode::Recursive)?; } if scripts_dir.exists() { watcher.watch(&scripts_dir, RecursiveMode::Recursive)?; } tokio::spawn(async move { loop { if rx.recv().await.is_none() { break; } // Debounce: a single save often fires several events in quick succession. tokio::time::sleep(Duration::from_millis(200)).await; while rx.try_recv().is_ok() {} reload_once(&config_dir, &routes, &scripts, &pools).await; } }); Ok(watcher) } async fn reload_once( config_dir: &PathBuf, routes: &Arc<ArcSwap<RouteTable>>, scripts: &Arc<ScriptEngine>, pools: &Arc<HashMap<String, DbPool>>, ) { let new_config = match config::load_config(config_dir) { Ok(c) => c, Err(e) => { tracing::error!(error = ?e, "hot reload: config invalid, keeping previous version"); return; } }; let table = match RouteTable::build(&new_config) { Ok(t) => t, Err(e) => { tracing::error!(error = ?e, "hot reload: failed to build route table, keeping previous version"); return; } }; if let Err(e) = db::ensure_tables(&new_config.endpoints, pools).await { tracing::error!(error = ?e, "hot reload: failed to auto-create table for a new endpoint, keeping previous version"); return; } routes.store(Arc::new(table)); scripts.invalidate_all(); tracing::info!(endpoints = new_config.endpoints.len(), "config hot-reloaded"); }