feat: automatically reload configuration when the config file is changed

This commit is contained in:
Ilya Zlobintsev
2024-03-17 13:47:45 +02:00
parent e74155515d
commit 16c50970f6
6 changed files with 154 additions and 4 deletions

87
Cargo.lock generated
View File

@@ -1241,6 +1241,26 @@ dependencies = [
"serde",
]
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "itoa"
version = "1.0.10"
@@ -1256,6 +1276,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kqueue"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]]
name = "lact"
version = "0.5.4"
@@ -1302,6 +1342,7 @@ dependencies = [
"libdrm_amdgpu_sys",
"libflate",
"nix",
"notify",
"os-release",
"pciid-parser",
"serde",
@@ -1514,6 +1555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
@@ -1531,6 +1573,23 @@ dependencies = [
"memoffset",
]
[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.2",
"filetime",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"walkdir",
"windows-sys 0.48.0",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -1927,6 +1986,15 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -2472,6 +2540,16 @@ dependencies = [
"vk-parse",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -2548,6 +2626,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@@ -38,3 +38,4 @@ tar = "0.4.40"
libflate = "2.0.0"
chrono = "0.4.31"
os-release = "0.1.0"
notify = { version = "6.1.1", default-features = false }

View File

@@ -3,10 +3,12 @@ use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind};
use anyhow::Context;
use lact_schema::{default_fan_curve, request::SetClocksCommand, FanControlMode, PmfwOptions};
use nix::unistd::getuid;
use notify::{RecommendedWatcher, Watcher};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::{collections::HashMap, env, fs, path::PathBuf};
use tracing::debug;
use tokio::sync::mpsc;
use tracing::{debug, error};
const FILE_NAME: &str = "config.yaml";
const DEFAULT_ADMIN_GROUPS: [&str; 2] = ["wheel", "sudo"];
@@ -158,6 +160,55 @@ impl Config {
}
}
pub fn start_watcher() -> mpsc::UnboundedReceiver<Config> {
let (config_tx, config_rx) = mpsc::unbounded_channel();
let (event_tx, event_rx) = std::sync::mpsc::channel();
tokio::task::spawn_blocking(move || {
let mut watcher = RecommendedWatcher::new(event_tx, notify::Config::default())
.expect("Could not create config file watcher");
let config_path = get_path();
let watch_path = config_path
.parent()
.expect("Config path always has a parent");
watcher
.watch(watch_path, notify::RecursiveMode::Recursive)
.expect("Could not subscribe to config file changes");
for res in event_rx {
debug!("got config file event {res:?}");
match res {
Ok(event) => {
use notify::EventKind;
if !event.paths.contains(&config_path) {
continue;
}
match event.kind {
EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_) => {
match Config::load() {
Ok(Some(new_config)) => config_tx.send(new_config).unwrap(),
Ok(None) => error!("config was removed!"),
Err(err) => {
error!("could not read config after it was changed: {err:#}");
}
}
}
_ => (),
}
}
Err(err) => error!("filesystem event error: {err}"),
}
}
debug!("registered config file event listener at path {watch_path:?}");
});
config_rx
}
fn get_path() -> PathBuf {
let uid = getuid();
if uid.is_root() {

View File

@@ -54,6 +54,7 @@ pub fn run() -> anyhow::Result<()> {
let server = Server::new(config).await?;
let handler = server.handler.clone();
tokio::task::spawn_local(listen_config_changes(handler.clone()));
tokio::task::spawn_local(listen_exit_signals(handler.clone()));
tokio::task::spawn_local(suspend::listen_events(handler));
server.run().await;
@@ -102,6 +103,16 @@ async fn listen_exit_signals(handler: Handler) {
std::process::exit(0);
}
async fn listen_config_changes(handler: Handler) {
let mut rx = config::start_watcher();
while let Some(new_config) = rx.recv().await {
info!("config file was changed, reloading");
handler.config.replace(new_config);
handler.apply_current_config().await;
info!("configuration reloaded");
}
}
async fn ensure_sufficient_uptime() {
match get_uptime() {
Ok(current_uptime) => {

View File

@@ -102,7 +102,7 @@ impl<'a> Handler {
config: Rc::new(RefCell::new(config)),
confirm_config_tx: Rc::new(RefCell::new(None)),
};
handler.load_config().await;
handler.apply_current_config().await;
// Eagerly release memory
// `load_controllers` allocates and deallocates the entire PCI ID database,
@@ -114,7 +114,7 @@ impl<'a> Handler {
Ok(handler)
}
pub async fn load_config(&self) {
pub async fn apply_current_config(&self) {
let config = self.config.borrow().clone(); // Clone to avoid locking the RwLock on an await point
for (id, gpu_config) in &config.gpus {

View File

@@ -10,7 +10,7 @@ pub async fn listen_events(handler: Handler) {
Ok(mut stream) => {
while stream.next().await.is_some() {
info!("suspend/resume event detected, reloading config");
handler.load_config().await;
handler.apply_current_config().await;
}
}
Err(err) => error!("could not subscribe to suspend events: {err:#}"),