libnyanpasu/clash-nyanpasu · error
write config timed out after {timeout:?}
Error message
write config timed out after {timeout:?} What it means
State::upsert maps WithEffectError::EffectTimedOut to UpsertError::WriteConfig with this message: the coordinated 'write config' effect did not finish within the given timeout, so the operation is reported as failed (state side already committed or rolled back per coordinator policy).
Source
Thrown at backend/nyanpasu-core/src/state/manager/persistent_builder.rs:220
formatter.serialize(&mut buf, &builder_for_save, config_prefix.as_deref())?;
let file = AtomicFile::new(&config_path, AllowOverwrite);
tokio::task::spawn_blocking(move || file.write(|f| f.write_all(&buf)))
.await?
.with_context(|| format!("failed to write config: {config_path}"))?;
Ok::<_, anyhow::Error>(())
})
.await;
match result {
Ok(((), report)) => {
self.current_builder = builder;
Ok(report)
}
Err(e) => match e {
WithEffectError::State(e) => Err(UpsertError::State(e)),
WithEffectError::Effect(e) => Err(UpsertError::WriteConfig(e)),
WithEffectError::EffectTimedOut(timeout) => Err(UpsertError::WriteConfig(
anyhow::anyhow!("write config timed out after {timeout:?}"),
)),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{Ack, StateAckSubscriber, StateChange, SubscriberName};
use serde::{Deserialize, Serialize};
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use tempfile::tempdir;
use tokio::fs;
View on GitHub (pinned to f7dbce2997)
Solutions
- Increase the timeout passed to the effect call if the write is legitimately slow.
- Investigate why the effect is slow (core responsiveness, disk I/O, cross-actor round-trips).
- Retry the upsert — check whether state was committed despite the timeout and reconcile if needed.
- Avoid synchronous cross-actor cycles in the effect path that can deadlock until timeout.
Example fix
// before
let report = manager.upsert(patch).await?;
// after
match manager.upsert(patch).await {
Ok(report) => Ok(report),
Err(UpsertError::WriteConfig(e)) if e.to_string().contains("timed out") => {
log::warn!("config write timed out; retrying once: {e}");
manager.upsert(patch).await.map_err(Into::into)
}
Err(e) => Err(e.into()),
} Defensive patterns
Strategy: retry
Validate before calling
// verify dependencies respond before a timed upsert
if tokio::time::timeout(Duration::from_secs(2), core_client.ping()).await.is_err() {
eprintln!("core unresponsive; upsert likely to time out");
} Try / catch
match manager.upsert(patch).await {
Err(UpsertError::WriteConfig(e)) if e.to_string().contains("timed out") => {
tokio::time::sleep(Duration::from_secs(1)).await;
manager.upsert(patch).await // retry; verify state consistency after
}
other => other,
} Prevention
- Set timeouts generously enough for slow disks and core restarts
- Avoid cross-actor synchronous cycles that stall effect execution
- After a timeout, check whether the config was actually written before retrying
- Monitor core responsiveness and degrade early instead of blocking
When it happens
Trigger: Calling upsert on the persistent state manager while the inner effect (runtime config write / patch via coordinator) exceeds the timeout — slow disk, blocked core, or deadlock in the effect path.
Common situations: Heavy config rebuilds, core restart taking longer than the timeout, or system under load causing the effect future to stall.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- IPC server is already initialized
- Invalid theme color: {}
- application actor call timed out
- clash config actor call timed out
- session state actor call timed out
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/0c78f1f4b8e2defc.
Report an issue: GitHub.