libnyanpasu/clash-nyanpasu · error
application actor call timed out
Error message
application actor call timed out
What it means
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.
Source
Thrown at backend/tauri/src/client/application.rs:114
.await
}
pub(crate) async fn prepare_replace(
&self,
state: NyanpasuAppConfig,
) -> anyhow::Result<PreparedTypedReplace<NyanpasuAppConfig>> {
match self
.inner
.actor_ref
.call(
|reply| ApplicationActorMessage::PrepareReplace { state, reply },
None,
)
.await?
{
CallResult::Success(result) => result,
CallResult::SenderError => anyhow::bail!("application actor reply dropped"),
CallResult::Timeout => anyhow::bail!("application actor call timed out"),
}
}
pub(crate) async fn replace_prepared_if_version(
&self,
expected_version: u64,
prepared: PreparedTypedReplace<NyanpasuAppConfig>,
) -> anyhow::Result<ConditionalReplaceResult<ApplicationSnapshot>> {
match self
.inner
.actor_ref
.call(
|reply| ApplicationActorMessage::ReplacePreparedIfVersion {
expected_version,
prepared,
reply,
},
None,View on GitHub (pinned to f7dbce2997)
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.
Example fix
// before
match self.inner.actor_ref.call(
|reply| ApplicationActorMessage::PrepareReplace { state, reply },
None,
).await? { ... }
// after
match self.inner.actor_ref.call(
|reply| ApplicationActorMessage::PrepareReplace { state, reply },
Some(Duration::from_secs(10)),
).await? {
CallResult::Success(r) => r,
CallResult::SenderError => anyhow::bail!("application actor reply dropped"),
CallResult::Timeout => anyhow::bail!("application actor call timed out after 10s"),
} Defensive patterns
Strategy: retry
Validate before calling
// Only attempt if a client exists and no stop was requested
if actor_alive.load(Ordering::Acquire) {
// proceed with replace_if_version
} Type guard
fn is_actor_timeout(err: &anyhow::Error) -> bool {
err.to_string().contains("application actor call timed out")
} Try / catch
match client.replace_if_version(ver, state).await {
Ok(res) => handle(res),
Err(e) if is_actor_timeout(&e) => schedule_retry_with_backoff(e),
Err(e) => report(e),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- clash config actor call timed out
- session state actor call timed out
- clash config actor reply dropped
- session state actor reply dropped
- Clash stream actor timed out
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/b9bf0de99e09d06c.
Report an issue: GitHub.