Search code examples
filerustwatchnotify

How to hot reload a struct from file changes


I'm trying to do hot reloading when a file changes, but I'm getting this error: expected a closure that implements the Fn trait, but this closure only implements FnMut this closure implements FnMut, not Fn

Seems to be unhappy with the closure I'm passing to the new_immediate function from this library:

notify = { version = "5.0.0-pre.4", features = ["serde"] }

My code:

use announcer::messages::{load_config, save_config, Config, Message};
use notify::{
    event::ModifyKind, Error, Event, EventFn, EventKind, RecommendedWatcher, RecursiveMode, Watcher,
};
use tide::{http, Body, Response};

const CONFIG_PATH: &str = "config.json";

#[async_std::main]
async fn main() -> tide::Result<()> {
    let mut config = load_config(CONFIG_PATH).unwrap();

    let mut watcher: RecommendedWatcher =
        Watcher::new_immediate(|result: Result<Event, Error>| {
            let event = result.unwrap();
            
            if event.kind == EventKind::Modify(ModifyKind::Any) {
                config = load_config(CONFIG_PATH).unwrap();
            }
        })?;

    watcher.watch(CONFIG_PATH, RecursiveMode::Recursive)?;

    let mut app = tide::with_state(config);
    app.listen("127.0.0.1:8080").await?;

    Ok(())
}

I asked in the Rust Discord beginners chat and 17cupsofcoffee said I should be using a mutex but I have no idea how to do that.


Solution

  • The issue here is that you're spawning this watching function in another thread, and you might write to it in one thread while reading to it in another, causing a race condition. You should use a Mutex, and lock it to get a guard that lets you read from it/write to it. Since tide's global state also needs Clone, you should also wrap it in an Arc, a thread-safe reference-counted pointer:

    use announcer::messages::{load_config, save_config, Config, Message};
    use notify::{
        event::ModifyKind, Error, Event, EventFn, EventKind, RecommendedWatcher, RecursiveMode, Watcher,
    };
    use tide::{http, Body, Response};
    // necessary imports
    use std::sync::{Arc, Mutex};
    
    const CONFIG_PATH: &str = "config.json";
    
    #[async_std::main]
    async fn main() -> tide::Result<()> {
        // we store it in an Arc<Mutex<T>>
        let config = Arc::new(Mutex::new(load_config(CONFIG_PATH).unwrap()));
        let cloned_config = Arc::clone(&config);
    
        let mut watcher: RecommendedWatcher =
            Watcher::new_immediate(move |result: Result<Event, Error>| {
                let event = result.unwrap();
                
                if event.kind == EventKind::Modify(ModifyKind::Any) {
                    // we lock the mutex to acquire a mutable guard and write to that
                    match load_config(CONFIG_PATH) {
                        Ok(new_config) => *cloned_config.lock().unwrap() = new_config,
                        Err(error) => println!("Error reloading config: {:?}", error),
                    }
            })?;
    
        watcher.watch(CONFIG_PATH, RecursiveMode::Recursive)?;
        
        let mut app = tide::with_state(config);
        app.listen("127.0.0.1:8080").await?;
    
        Ok(())
    }
    

    Then, whenever you need to read from the state, you just lock the mutex:

    let config_guard = req.state().lock().unwrap();
    println!("{:?}", config_guard.foo);
    

    For additional reference: