{"record":{"id":"6821b8568ba98882","repo":"libnyanpasu/clash-nyanpasu","slug":"clash-config-actor-call-timed-out","errorCode":null,"errorMessage":"clash config actor call timed out","messagePattern":"clash config actor call timed out","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/client/clash_config.rs","lineNumber":127,"sourceCode":"            .await\n    }\n\n    pub(crate) async fn prepare_replace(\n        &self,\n        state: ClashConfig,\n    ) -> anyhow::Result<PreparedTypedReplace<ClashConfig>> {\n        match self\n            .inner\n            .actor_ref\n            .call(\n                |reply| ClashConfigActorMessage::PrepareReplace { state, reply },\n                None,\n            )\n            .await?\n        {\n            CallResult::Success(result) => result,\n            CallResult::SenderError => anyhow::bail!(\"clash config actor reply dropped\"),\n            CallResult::Timeout => anyhow::bail!(\"clash config actor call timed out\"),\n        }\n    }\n\n    pub(crate) async fn replace_prepared_if_version(\n        &self,\n        expected_version: u64,\n        prepared: PreparedTypedReplace<ClashConfig>,\n    ) -> anyhow::Result<ConditionalReplaceResult<ClashConfigSnapshot>> {\n        match self\n            .inner\n            .actor_ref\n            .call(\n                |reply| ClashConfigActorMessage::ReplacePreparedIfVersion {\n                    expected_version,\n                    prepared,\n                    reply,\n                },\n                None,","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/client/clash_config.rs#L109-L145","documentation":"This error is raised by `ClashConfigClient::prepare_replace` when the ractor `actor_ref.call(...)` returns `CallResult::Timeout`. `prepare_replace` passes `None` for the timeout, so ractor uses its default (10s) RPC timeout; if the actor does not reply within that window the future resolves to `CallResult::Timeout` and the client bails. The prepare step (serializing state through the legacy bridge) never produced a `PreparedTypedReplace`.","triggerScenarios":"Calling `replace_if_version` → `prepare_replace` while the ClashConfigActor's mailbox is blocked by a long-running earlier message (e.g. a slow `Patch`, `Replace`, or a hung persistence write / legacy bridge call), or the actor is dead in a way that never resolves the call (rare), or the actor is stuck awaiting a locked resource during a slow disk flush of clash-config.yaml.","commonSituations":"Slow disk or very large clash config making persistence exceed the default RPC timeout; a deadlock between actors (e.g. ClashConfigActor synchronously waiting on another actor that is waiting on it); system under heavy load or suspended; many queued config operations serializing ahead of the prepare request.","solutions":["Check for cross-actor synchronous cycles or long-running handlers in ClashConfigActor that block the mailbox ahead of PrepareReplace.","Reduce slow work inside handlers (move large disk writes or heavy bridge work off the reply path) so RPCs reply promptly.","Pass an explicit, generous timeout instead of `None` to `actor_ref.call` in `prepare_replace` if legitimate slow prepares must be tolerated.","Retry the `replace_if_version` call once the system load or blocking operation has cleared; verify no actor is deadlocked."],"exampleFix":"// before\nself.inner.actor_ref.call(\n    |reply| ClashConfigActorMessage::PrepareReplace { state, reply },\n    None,\n).await?\n// after\nself.inner.actor_ref.call(\n    |reply| ClashConfigActorMessage::PrepareReplace { state, reply },\n    Some(Duration::from_secs(30)),\n).await?","handlingStrategy":"retry","validationCode":"// Pre-check actor responsiveness with a cheap bounded call\nmatch tokio::time::timeout(Duration::from_secs(2), client.get()).await {\n    Ok(Ok(_)) => { /* actor healthy, proceed with prepare_replace */ }\n    _ => anyhow::bail!(\"clash config actor unresponsive; not attempting prepare_replace\"),\n}","typeGuard":null,"tryCatchPattern":"match client.replace_if_version(expected_version, next).await {\n    Err(e) if e.to_string().contains(\"timed out\") => {\n        // bounded retry with backoff; do not retry unbounded\n        tokio::time::sleep(Duration::from_secs(1)).await;\n        client.replace_if_version(expected_version, next).await\n    }\n    other => other,\n}","preventionTips":["Keep actor handlers short; move large disk writes off the synchronous reply path.","Avoid synchronous cross-actor request cycles that can deadlock the mailbox.","Pass an explicit timeout suited to worst-case persistence time instead of relying on ractor's default.","Monitor actor mailbox depth / handler durations in tests under load."],"tags":["actor","rpc","timeout","rust","ractor"],"backgroundTag":"request-timeout","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}