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

  1. Retry once; transient queueing is the most common cause.
  2. Increase the timeout for prepare_replace calls.
  3. Audit the PrepareReplace handler for blocking I/O or synchronous calls into other actors (possible deadlock) and make it async/non-blocking.
  4. 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

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.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/05e86ee3f976759b. Report an issue: GitHub.