1use super::{Config, ConfigError};
8use parking_lot::RwLock;
9use std::fs;
10use std::path::Path;
11use std::sync::Arc;
12use tokio::sync::mpsc;
13
14pub const HOT_RELOAD_POLL_INTERVAL: std::time::Duration =
16 std::time::Duration::from_millis(250);
17
18type Fingerprint = (Option<std::time::SystemTime>, u64);
20
21fn file_fingerprint(path: &Path) -> std::io::Result<Fingerprint> {
22 Ok(fingerprint_of(&fs::metadata(path)?))
23}
24
25async fn file_fingerprint_async(
27 path: &Path,
28) -> std::io::Result<Fingerprint> {
29 Ok(fingerprint_of(&tokio::fs::metadata(path).await?))
30}
31
32fn fingerprint_of(meta: &fs::Metadata) -> Fingerprint {
33 (meta.modified().ok(), meta.len())
34}
35
36impl Config {
37 pub fn hot_reload_async(
52 config_path: &str,
53 config: &Arc<RwLock<Self>>,
54 ) -> Result<mpsc::Sender<()>, ConfigError> {
55 let mut seen = file_fingerprint(Path::new(config_path))
56 .map_err(ConfigError::WatcherError)?;
57 let (stop_tx, mut stop_rx) = mpsc::channel::<()>(1);
58 let config = Arc::clone(config);
59 let path = config_path.to_string();
60 tokio::spawn(async move {
61 let mut ticks =
62 tokio::time::interval(HOT_RELOAD_POLL_INTERVAL);
63 ticks.set_missed_tick_behavior(
64 tokio::time::MissedTickBehavior::Delay,
65 );
66 loop {
67 tokio::select! {
68 _ = ticks.tick() => {
69 Self::reload_if_changed(&path, &mut seen, &config).await;
70 }
71 _ = stop_rx.recv() => break,
72 }
73 }
74 });
75 Ok(stop_tx)
76 }
77
78 async fn reload_if_changed(
81 path: &str,
82 seen: &mut Fingerprint,
83 config: &Arc<RwLock<Self>>,
84 ) {
85 let Ok(now) = file_fingerprint_async(Path::new(path)).await
86 else {
87 return;
88 };
89 if now == *seen {
90 return;
91 }
92 *seen = now;
93 if let Ok(new_config) = Self::load_async(Some(path)).await {
94 let fresh = new_config.read().clone();
95 *config.write() = fresh;
96 }
97 }
98}