Make live sync configurable

This commit is contained in:
Eric Wendland 2026-05-20 13:34:16 +02:00
commit 3eb6b623e7
6 changed files with 106 additions and 14 deletions

View file

@ -13,6 +13,10 @@ local_discovery = true
#
# [iroh.relay_maps.home]
# urls = ["https://relay.example.com"]
#
[sync]
live_sync_enabled = true
live_sync_interval_ms = 30000
"#;
#[derive(Clone, Debug)]
@ -116,11 +120,16 @@ pub enum ConfigError {
EmptyRelayMap(String),
#[error("invalid iroh relay URL in map `{map}`: {url}")]
InvalidRelayUrl { map: String, url: String },
#[error("invalid sync live_sync_enabled value: {0}")]
InvalidLiveSyncEnabled(String),
#[error("invalid sync live_sync_interval_ms value: {0}")]
InvalidLiveSyncInterval(String),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct GethConfig {
pub iroh: IrohConfig,
pub sync: SyncConfig,
}
impl GethConfig {
@ -140,6 +149,7 @@ impl GethConfig {
let document = text.parse::<toml_edit::DocumentMut>()?;
let relay_maps = parse_relay_maps(&document)?;
let local_discovery = parse_local_discovery(&document)?;
let sync = parse_sync_config(&document)?;
let relay_mode = match document.get("iroh").and_then(|iroh| iroh.get("relay_mode")) {
Some(item) => {
let value = item
@ -156,6 +166,7 @@ impl GethConfig {
relay_maps,
local_discovery,
},
sync,
})
}
}
@ -177,6 +188,21 @@ impl Default for IrohConfig {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyncConfig {
pub live_sync_enabled: bool,
pub live_sync_interval_ms: u64,
}
impl Default for SyncConfig {
fn default() -> Self {
Self {
live_sync_enabled: true,
live_sync_interval_ms: 30_000,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RelayMapConfig {
pub urls: Vec<String>,
@ -297,6 +323,37 @@ fn parse_local_discovery(document: &toml_edit::DocumentMut) -> Result<bool, Conf
}
}
fn parse_sync_config(document: &toml_edit::DocumentMut) -> Result<SyncConfig, ConfigError> {
let live_sync_enabled = match document
.get("sync")
.and_then(|sync| sync.get("live_sync_enabled"))
{
Some(item) => item
.as_bool()
.ok_or_else(|| ConfigError::InvalidLiveSyncEnabled(item.to_string()))?,
None => true,
};
let live_sync_interval_ms = match document
.get("sync")
.and_then(|sync| sync.get("live_sync_interval_ms"))
{
Some(item) => {
let interval = item
.as_integer()
.ok_or_else(|| ConfigError::InvalidLiveSyncInterval(item.to_string()))?;
if interval < 100 {
return Err(ConfigError::InvalidLiveSyncInterval(interval.to_string()));
}
interval as u64
}
None => 30_000,
};
Ok(SyncConfig {
live_sync_enabled,
live_sync_interval_ms,
})
}
#[cfg(test)]
mod tests {
use super::*;
@ -306,6 +363,8 @@ mod tests {
let config = GethConfig::parse(GethConfig::default_toml()).expect("parse config");
assert_eq!(config.iroh.relay_mode, RelayMode::Default);
assert!(config.iroh.local_discovery);
assert!(config.sync.live_sync_enabled);
assert_eq!(config.sync.live_sync_interval_ms, 30_000);
}
#[test]
@ -381,6 +440,28 @@ mod tests {
assert!(!config.iroh.local_discovery);
}
#[test]
fn config_parses_live_sync_settings() {
let config =
GethConfig::parse("[sync]\nlive_sync_enabled = false\nlive_sync_interval_ms = 250\n")
.expect("parse");
assert!(!config.sync.live_sync_enabled);
assert_eq!(config.sync.live_sync_interval_ms, 250);
}
#[test]
fn config_rejects_invalid_live_sync_settings() {
let error = GethConfig::parse("[sync]\nlive_sync_enabled = \"yes\"\n").expect_err("error");
assert!(matches!(error, ConfigError::InvalidLiveSyncEnabled(_)));
let error = GethConfig::parse("[sync]\nlive_sync_interval_ms = 99\n").expect_err("error");
assert!(matches!(error, ConfigError::InvalidLiveSyncInterval(_)));
let error =
GethConfig::parse("[sync]\nlive_sync_interval_ms = \"fast\"\n").expect_err("error");
assert!(matches!(error, ConfigError::InvalidLiveSyncInterval(_)));
}
#[test]
fn config_rejects_non_bool_local_discovery() {
let error = GethConfig::parse("[iroh]\nlocal_discovery = \"yes\"\n").expect_err("error");

View file

@ -153,7 +153,6 @@ struct PipeRuntime {
}
const PUBSUB_RING_LIMIT: usize = 256;
const LIVE_SYNC_INTERVAL: Duration = Duration::from_secs(30);
const PIPE_CONNECTION_RING_LIMIT: usize = 256;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
@ -205,7 +204,13 @@ pub async fn run_daemon(paths: GethPaths) -> Result<(), NodeError> {
.clone()
{
spawn_iroh_control_accept_loop(node.clone(), endpoint);
spawn_background_live_sync(node.clone());
let config = GethConfig::load(&node.paths.config_file())?;
if config.sync.live_sync_enabled {
spawn_background_live_sync(
node.clone(),
Duration::from_millis(config.sync.live_sync_interval_ms),
);
}
}
let _peer_card_lan_discovery = start_peer_card_lan_discovery(&node).await;
if Path::new(&paths.socket_path()).exists() {
@ -2134,12 +2139,12 @@ fn spawn_iroh_control_accept_loop(node: LocalNode, endpoint: GethIrohEndpoint) {
});
}
fn spawn_background_live_sync(node: LocalNode) {
fn spawn_background_live_sync(node: LocalNode, interval_duration: Duration) {
tokio::spawn(async move {
if let Err(error) = run_live_sync_once(&node).await {
tracing::debug!(%error, "initial live sync tick failed");
}
let mut interval = tokio::time::interval(LIVE_SYNC_INTERVAL);
let mut interval = tokio::time::interval(interval_duration);
loop {
interval.tick().await;
if let Err(error) = run_live_sync_once(&node).await {