libnyanpasu/clash-nyanpasu · error
session state actor call timed out
Error message
session state actor call timed out
What it means
Raised in SessionStateClient::prepare_replace (backend/tauri/src/client/session_state.rs:114) when the PrepareReplace RPC times out (CallResult::Timeout): the session-state actor did not answer within the allotted (or default) timeout. Points to a busy, blocked, or deadlocked actor rather than a dropped reply.
Source
Thrown at backend/tauri/src/client/session_state.rs:114
.await
}
pub(crate) async fn prepare_replace(
&self,
state: PersistentState,
) -> anyhow::Result<PreparedTypedReplace<PersistentState>> {
match self
.inner
.actor_ref
.call(
|reply| SessionStateActorMessage::PrepareReplace { state, reply },
None,
)
.await?
{
CallResult::Success(result) => result,
CallResult::SenderError => anyhow::bail!("session state actor reply dropped"),
CallResult::Timeout => anyhow::bail!("session state actor call timed out"),
}
}
pub(crate) async fn replace_prepared_if_version(
&self,
expected_version: u64,
prepared: PreparedTypedReplace<PersistentState>,
) -> anyhow::Result<ConditionalReplaceResult<SessionStateSnapshot>> {
match self
.inner
.actor_ref
.call(
|reply| SessionStateActorMessage::ReplacePreparedIfVersion {
expected_version,
prepared,
reply,
},
None,View on GitHub (pinned to f7dbce2997)
Solutions
- Retry once; transient queueing is the most common cause.
- Increase the timeout for prepare_replace calls.
- Audit the PrepareReplace handler for blocking I/O or synchronous calls into other actors (possible deadlock) and make it async/non-blocking.
- Check actor logs and mailbox depth to confirm overload and add backpressure upstream.
Example fix
// before client.prepare_replace(state).await?; // default/short timeout // after client.prepare_replace_with_timeout(state, Duration::from_secs(10)).await?;
Defensive patterns
Strategy: retry
Try / catch
match client.prepare_replace(state).await {
Ok(p) => p,
Err(e) if e.to_string().contains("call timed out") => {
tokio::time::sleep(Duration::from_millis(250)).await;
client.prepare_replace(state).await? // retry once
}
Err(e) => return Err(e),
} Prevention
- Give prepare_replace a timeout sized for worst-case persistence latency
- Keep the PrepareReplace handler non-blocking (spawn_blocking for fsync-heavy work)
- Avoid synchronous cross-actor calls from the session-state handler
- Add a liveness probe (cheap get) before long config operations
When it happens
Trigger: replace_if_version calls prepare_replace; actor_ref.call returns CallResult::Timeout because the actor never sent on the RpcReplyPort in time — e.g. it is stuck on slow persistence or queued behind many messages.
Common situations: Actor mailbox flooded with updates; prepare does synchronous disk I/O on a slow filesystem; synchronous cross-actor cycle stalling the handler; caller timeout set far below realistic handler latency.
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
- 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/05e86ee3f976759b.
Report an issue: GitHub.