libnyanpasu/clash-nyanpasu · error
proxy actor timed out; an operation may still be running, do
Error message
proxy actor timed out; an operation may still be running, do not replay mutations automatically
What it means
Thrown by the proxies service `call` helper when a request/reply call to the underlying ractor proxy actor times out (120 seconds). The actor may still be processing the operation in the background, so callers are explicitly warned not to automatically replay the mutation — a retry could double-apply it. This is a timeout guard against a stuck or overloaded actor rather than a definitive failure of the operation.
Source
Thrown at backend/tauri/src/core/proxies.rs:328
.await?;
Ok(Self(Arc::new(ClientInner {
actor,
snapshots: snapshot_rx,
changes: changes_rx,
})))
}
async fn call<T: Send + 'static>(
&self,
message: impl FnOnce(RpcReplyPort<Result<T>>) -> Message,
) -> Result<T> {
match self
.0
.actor
.call(message, Some(Duration::from_secs(120)))
.await
{
Ok(ractor::rpc::CallResult::Success(result)) => result,
Ok(ractor::rpc::CallResult::Timeout) => anyhow::bail!(
"proxy actor timed out; an operation may still be running, do not replay mutations automatically"
),
_ => anyhow::bail!("proxy actor is unavailable"),
}
}
pub async fn get(&self, force: bool) -> Result<Proxies> {
let snapshot = self.call(|reply| Message::Read { force, reply }).await?;
anyhow::ensure!(
!snapshot.api.is_revoked(),
"proxy snapshot belongs to a retired instance"
);
Ok(snapshot.proxies.clone())
}
pub async fn providers(&self) -> Result<api::ProvidersProxiesRes> {
let snapshot = self
.call(|reply| Message::Read {
force: false,
reply,View on GitHub (pinned to f7dbce2997)
Solutions
- Do not auto-retry mutations; first check current state with a read operation (`get`) to see whether the original operation actually completed.
- Retry the read/idempotent operations freely; for `select`/`update_provider`, wait and verify state before re-issuing manually.
- If the actor is persistently stuck, restart the core/actor via the app's restart mechanism and check network reachability of provider URLs.
Example fix
// before: blind retry on any error
match client.update_provider(name).await {
Err(_) => client.update_provider(name).await?, // may double-apply
Ok(v) => v,
}
// after: timeout means 'may still be running' — verify first
match client.update_provider(name).await {
Err(e) if e.to_string().contains("timed out") => {
let snapshot = client.get_proxies().await?; // check state before manual retry
}
other => other?,
} Defensive patterns
Strategy: retry
Validate before calling
// Cheap liveness probe with a short timeout before long mutations
match tokio::time::timeout(
Duration::from_secs(5),
client.get_proxies(),
).await {
Ok(Ok(_)) => {} // actor responsive, safe to proceed
_ => eprintln!("proxy actor slow or stuck; avoid mutating calls now"),
} Try / catch
match client.update_provider(name).await {
Err(e) if e.to_string().contains("proxy actor timed out") => {
// do NOT auto-retry; verify state, then retry manually with backoff
let state = client.get_proxies().await?;
}
other => other?,
} Prevention
- Never auto-replay mutations (select/update_provider) after a timeout; always verify state first
- Set generous timeouts on provider downloads so the actor isn't blocked on slow networks
- Monitor actor mailbox depth / latency and alert before the 120s cap
- Read operations are safe to retry with backoff; keep mutations manual after timeouts
When it happens
Trigger: Any of `get`, `providers`, `select`, or `update_provider` calling `call(...)` when `actor.call(message, Some(Duration::from_secs(120)))` returns `ractor::rpc::CallResult::Timeout` — i.e. the proxy actor did not reply within 120 seconds.
Common situations: A provider update/download is slow or hung (large rule/provider download, stalled network); the actor is blocked on a long sequential operation ahead of yours; the core API is unresponsive while the actor waits on it.
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
- application actor call timed out
- clash config actor call timed out
- session state actor call timed out
- Clash stream actor timed out
- proxy actor is unavailable
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/1c7ce7fcb495381a.
Report an issue: GitHub.