#hot-reload #config-file #service #kvs #define #periodic #traits #reloader #hot #notifier

hot_reload

Trait and service definition of periodic hot reloader and notifier for config-file, KVS, etc

11 releases

Uses new Rust 2024

new 0.2.0 Jun 30, 2025
0.1.9 Apr 1, 2025
0.1.8 Nov 5, 2024
0.1.6 Jul 16, 2024
0.1.4 Jul 21, 2023

#609 in Rust patterns

Download history 278/week @ 2025-03-14 118/week @ 2025-03-21 299/week @ 2025-03-28 128/week @ 2025-04-04 115/week @ 2025-04-11 82/week @ 2025-04-18 172/week @ 2025-04-25 178/week @ 2025-05-02 192/week @ 2025-05-09 229/week @ 2025-05-16 150/week @ 2025-05-23 140/week @ 2025-05-30 71/week @ 2025-06-06 84/week @ 2025-06-13 31/week @ 2025-06-20 252/week @ 2025-06-27

447 downloads per month

MIT license

14KB
207 lines

Periodic hot reloader and notifier for files, KVS, etc. for Rust

hot_reload hot_reload License: MIT

This provides a Rust trait definition and service library for hot-reloading your files, KVS, etc. by periodically checking the system.

Reload Trait Definition

To use this library, you need to prepare your own struct implementing reloader::Reload trait, defined as follows:

#[async_trait]
/// Trait defining the responsibility of reloaders to periodically load the target value `V` from `Source`.
/// Source could be a file, a KVS, whatever if you can implement `Reload<V, S>` with `Reload<V, S>::Source`.
/// The generic parameters allow for flexible error handling and value types.
pub trait Reload<V, S = &'static str>
where
  V: Eq + PartialEq,
  S: Into<std::borrow::Cow<'static, str>> + std::fmt::Display,
{
  type Source;
  async fn new(src: &Self::Source) -> ReloadResult<Self, V, S>
  where
    Self: Sized;
  async fn reload(&self) -> ReloadResult<Option<V>, V, S>;
}

This trait defines the source type (file, KVS, etc) and reloaded object type V. The generic parameter S allows for flexible error message types. The following is an example of periodic-reloading a config-file through a given file path string.

pub struct ConfigReloader {
  pub config_path: PathBuf,
}

#[async_trait]
impl Reload<ServerConfig> for ConfigReloader {
  type Source = String;
  async fn new(source: &Self::Source) -> ReloadResult<Self, ServerConfig> {
    Ok(Self {
      config_path: PathBuf::from(source),
    })
  }

  async fn reload(&self) -> ReloadResult<Option<ServerConfig>, ServerConfig> {
    let config_str = std::fs::read_to_string(&self.config_path)
      .map_err(|e| ReloaderError::Reload(format!("Failed to read config file: {}", e)))?;
    let config: ServerConfig = config_object_from_str(config_str)
      .map_err(|e| ReloaderError::Reload(format!("Failed to parse config: {}", e)))?;

    Ok(Some(config))
  }
}

Configuration

The ReloaderConfig struct allows you to customize the reloader behavior:

pub struct ReloaderConfig {
  /// Period between reload attempts in seconds
  pub watch_delay_sec: u32,
  /// If true, broadcast updates even when values haven't changed
  pub force_reload: bool,
}

The service provides several convenience methods for common configurations:

  • with_defaults(): Uses default config (10 second delay, no force reload)
  • with_delay(delay_sec): Custom delay with no force reload
  • new(source, config): Full control over configuration

Usage

use hot_reload::*;

// Create reloader service with default configuration (10 second interval)
let (service, mut rx) = ReloaderService::<ConfigReloader, ServerConfig>::with_defaults(&config_path).await.unwrap();

// Or create with custom configuration
let config = ReloaderConfig {
    watch_delay_sec: 5,
    force_reload: false,
};
let (service, mut rx) = ReloaderService::<ConfigReloader, ServerConfig>::new(&config_path, config).await.unwrap();

// Start the reloader service in a background task
tokio::spawn(async move { service.start().await });

// Main event loop
loop {
  tokio::select! {
    // Add main logic of the event loop with up-to-date value
    _ = something.happened() => {
      // ...
    }
    // immediately update if watcher detects the change
    _ = rx.changed() => {
      if let Some(value) = rx.get() {
        info!("Received updated value: {:?}", value);
      } else {
        break; // Service terminated
      }
    }
    else => break
  }
}

Dependencies

~2–11MB
~106K SLoC