use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use notify::Event;
use crate::error::Error;
#[cfg(not(feature = "tracing"))]
use crate::log::info;
use crate::log::warning;
use super::relevance::is_relevant;
use super::Watched;
const ATOMIC_SAVE_GRACE: Duration = Duration::from_millis(25);
pub(super) fn run(
name: &'static str,
watched: &Watched,
debounce: Duration,
reload: impl Fn() -> Result<Option<String>, Error>,
receiver: &mpsc::Receiver<notify::Result<Event>>,
) {
loop {
match collect_relevant(receiver, name, debounce, watched) {
Collected::Dirty => {}
Collected::Disconnected => {
return;
}
}
thread::sleep(ATOMIC_SAVE_GRACE);
#[cfg(feature = "tracing")]
let _span = ::tracing::info_span!(target: "dynamic_config", "config_reload", config = name)
.entered();
let started = std::time::Instant::now();
let outcome = reload();
let duration_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX);
#[cfg(feature = "tracing")]
match &outcome {
Ok(summary) => ::tracing::info!(
target: "dynamic_config",
config = name,
outcome = "reloaded",
duration_ms,
summary = summary.as_deref().unwrap_or(""),
"{name}: reloaded in {duration_ms}ms"
),
Err(error) => ::tracing::warn!(
target: "dynamic_config",
config = name,
outcome = "failed",
duration_ms,
error = %error,
"{name}: reload failed in {duration_ms}ms, keeping the previous snapshot"
),
}
#[cfg(not(feature = "tracing"))]
match outcome {
Ok(Some(summary)) => info!("{name}: reloaded in {duration_ms}ms, {summary}"),
Ok(None) => info!("{name}: reloaded in {duration_ms}ms"),
Err(error) => warning!(
"{name}: reload failed after {duration_ms}ms, keeping the previous snapshot: \
{error}"
),
}
}
}
enum Collected {
Dirty,
Disconnected,
}
fn collect_relevant(
receiver: &mpsc::Receiver<notify::Result<Event>>,
name: &'static str,
debounce: Duration,
watched: &Watched,
) -> Collected {
loop {
match receiver.recv() {
Ok(Ok(event)) if is_relevant(&event, watched) => break,
Ok(Ok(_)) => {}
Ok(Err(error)) => warning!("{name}: watcher error: {error}"),
Err(mpsc::RecvError) => return Collected::Disconnected,
}
}
let deadline = std::time::Instant::now() + debounce.saturating_mul(4);
let mut quiet_until = std::time::Instant::now() + debounce;
loop {
let now = std::time::Instant::now();
let target = quiet_until.min(deadline);
if now >= target {
return Collected::Dirty;
}
match receiver.recv_timeout(target - now) {
Ok(Ok(event)) if is_relevant(&event, watched) => {
quiet_until = std::time::Instant::now() + debounce;
}
Ok(Ok(_)) => {}
Ok(Err(error)) => warning!("{name}: watcher error: {error}"),
Err(mpsc::RecvTimeoutError::Timeout) => return Collected::Dirty,
Err(mpsc::RecvTimeoutError::Disconnected) => return Collected::Disconnected,
}
}
}