{"record":{"id":"b9bf0de99e09d06c","repo":"libnyanpasu/clash-nyanpasu","slug":"application-actor-call-timed-out","errorCode":null,"errorMessage":"application actor call timed out","messagePattern":"application actor call timed out","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/client/application.rs","lineNumber":114,"sourceCode":"            .await\n    }\n\n    pub(crate) async fn prepare_replace(\n        &self,\n        state: NyanpasuAppConfig,\n    ) -> anyhow::Result<PreparedTypedReplace<NyanpasuAppConfig>> {\n        match self\n            .inner\n            .actor_ref\n            .call(\n                |reply| ApplicationActorMessage::PrepareReplace { state, reply },\n                None,\n            )\n            .await?\n        {\n            CallResult::Success(result) => result,\n            CallResult::SenderError => anyhow::bail!(\"application actor reply dropped\"),\n            CallResult::Timeout => anyhow::bail!(\"application 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<NyanpasuAppConfig>,\n    ) -> anyhow::Result<ConditionalReplaceResult<ApplicationSnapshot>> {\n        match self\n            .inner\n            .actor_ref\n            .call(\n                |reply| ApplicationActorMessage::ReplacePreparedIfVersion {\n                    expected_version,\n                    prepared,\n                    reply,\n                },\n                None,","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/client/application.rs#L96-L132","documentation":"prepare_replace() sends a PrepareReplace message to the ApplicationActor and awaits an RpcReplyPort response. ractor returns CallResult::Timeout when the actor did not answer within the allowed time, and this code converts that into anyhow::bail!(\"application actor call timed out\"). It means the application actor is alive-or-unknown but its reply never arrived: the actor is busy, blocked (e.g. on a slow persistence write or legacy-bridge call), or its message queue is backed up. It is a liveness failure of the actor RPC, not a data/validation error.","triggerScenarios":"Calling replace_if_version() (which calls prepare_replace) while the ApplicationActor is blocked processing a prior message (e.g. a long disk persistence or bridge prepare), or when the actor is overloaded/stopped but the reply port is still reachable. prepare_replace passes timeout=None to actor_ref.call, so the timeout comes from ractor's default call timeout behavior.","commonSituations":"App startup with a very large or slow application.yaml on a slow disk; the VergeLegacyBridge::prepare() hook doing blocking I/O; the actor mailbox flooded by rapid config writes; or running the call on a runtime starved of worker threads while the actor handler blocks a thread.","solutions":["Check that the ApplicationActor was spawned and is not stuck: log entry/exit of the PrepareReplace handler in ApplicationActor.","Reduce blocking work inside the actor handler (move slow disk/bridge work off the hot path or into spawn_blocking).","Retry the prepare_replace call with backoff; a transient stall often clears once the actor drains its queue.","If the actor is genuinely dead/stopped (client Drop calls actor_ref.stop), rebuild the ApplicationClient via ApplicationClient::new instead of retrying.","Increase the explicit timeout by passing a Some(Duration) instead of None if latency is expected to be high."],"exampleFix":"// before\nmatch self.inner.actor_ref.call(\n    |reply| ApplicationActorMessage::PrepareReplace { state, reply },\n    None,\n).await? { ... }\n// after\nmatch self.inner.actor_ref.call(\n    |reply| ApplicationActorMessage::PrepareReplace { state, reply },\n    Some(Duration::from_secs(10)),\n).await? {\n    CallResult::Success(r) => r,\n    CallResult::SenderError => anyhow::bail!(\"application actor reply dropped\"),\n    CallResult::Timeout => anyhow::bail!(\"application actor call timed out after 10s\"),\n}","handlingStrategy":"retry","validationCode":"// Only attempt if a client exists and no stop was requested\nif actor_alive.load(Ordering::Acquire) {\n    // proceed with replace_if_version\n}","typeGuard":"fn is_actor_timeout(err: &anyhow::Error) -> bool {\n    err.to_string().contains(\"application actor call timed out\")\n}","tryCatchPattern":"match client.replace_if_version(ver, state).await {\n    Ok(res) => handle(res),\n    Err(e) if is_actor_timeout(&e) => schedule_retry_with_backoff(e),\n    Err(e) => report(e),\n}","preventionTips":["Keep actor handlers non-blocking; push slow I/O to spawn_blocking","Pass explicit timeouts so deadlines are intentional","Limit concurrent writers to the application config actor","Monitor actor mailbox depth and handler latency","Recreate the client instead of retrying against a stopped actor"],"tags":["actor","timeout","rpc","rust"],"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"}