Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Async & Runtimes

The async attribute

#![allow(unused)]
fn main() {
#[dynamic_config(files = ["config.toml"], key = "db", watch, async)]
}

Generates load_async(), init_async() and changes(). Requires the async feature — which pulls in no runtime at all. See Async.

Async

The async feature brings in no runtime. changes() is a Future, so tokio, async-std, smol and a hand-written executor all drive it identically:

#![allow(unused)]
fn main() {
#[dynamic_config(files = ["config.toml"], key = "db", watch, async)]
#[derive(Debug, Deserialize)]
struct DbConfig { pool_size: u32 }

DbConfig::init_async().await?;
DbConfig::start_watch()?.detach();

let mut changes = DbConfig::changes();

spawn(async move {
    loop {
        let config = changes.changed().await;
        pool.resize(config.pool_size);
    }
});
}

The snapshot current when changes() is called counts as already seen, so the first changed().await waits for the next reload. Reloads that land while nothing is awaiting are not queued — waking up to the latest configuration is what a reader wants, and a queue would hand it stale ones first.

Where the blocking work goes

Reading configuration touches the filesystem, so load_async moves it off the executor. Where is the one genuinely runtime-specific part, so it is pluggable:

Setupload_async uses
tokio featuretokio::task::spawn_blocking
set_blocking_executor installedthat executor
neithera freshly spawned thread

A configuration load happens at startup and on reload, so a thread per call is a real answer rather than a placeholder. For async-std or smol, hand the crate its pool once:

#![allow(unused)]
fn main() {
struct AsyncStd;

impl BlockingExecutor for AsyncStd {
    fn execute(&self, work: Box<dyn FnOnce() + Send + 'static>) {
        async_std::task::spawn_blocking(work);
    }
}

dynamic_config::set_blocking_executor(AsyncStd)?;
}

The watcher itself stays on a plain thread whatever you choose: notify's channel is synchronous, and keeping it off the runtime means file watching works whether or not one is running.