libnyanpasu/clash-nyanpasu · error

clash config actor call timed out

Error message

clash config actor call timed out

What it means

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`.

Source

Thrown at backend/tauri/src/client/clash_config.rs:127

            .await
    }

    pub(crate) async fn prepare_replace(
        &self,
        state: ClashConfig,
    ) -> anyhow::Result<PreparedTypedReplace<ClashConfig>> {
        match self
            .inner
            .actor_ref
            .call(
                |reply| ClashConfigActorMessage::PrepareReplace { state, reply },
                None,
            )
            .await?
        {
            CallResult::Success(result) => result,
            CallResult::SenderError => anyhow::bail!("clash config actor reply dropped"),
            CallResult::Timeout => anyhow::bail!("clash config actor call timed out"),
        }
    }

    pub(crate) async fn replace_prepared_if_version(
        &self,
        expected_version: u64,
        prepared: PreparedTypedReplace<ClashConfig>,
    ) -> anyhow::Result<ConditionalReplaceResult<ClashConfigSnapshot>> {
        match self
            .inner
            .actor_ref
            .call(
                |reply| ClashConfigActorMessage::ReplacePreparedIfVersion {
                    expected_version,
                    prepared,
                    reply,
                },
                None,

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Check for cross-actor synchronous cycles or long-running handlers in ClashConfigActor that block the mailbox ahead of PrepareReplace.
  2. Reduce slow work inside handlers (move large disk writes or heavy bridge work off the reply path) so RPCs reply promptly.
  3. Pass an explicit, generous timeout instead of `None` to `actor_ref.call` in `prepare_replace` if legitimate slow prepares must be tolerated.
  4. Retry the `replace_if_version` call once the system load or blocking operation has cleared; verify no actor is deadlocked.

Example fix

// before
self.inner.actor_ref.call(
    |reply| ClashConfigActorMessage::PrepareReplace { state, reply },
    None,
).await?
// after
self.inner.actor_ref.call(
    |reply| ClashConfigActorMessage::PrepareReplace { state, reply },
    Some(Duration::from_secs(30)),
).await?
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check actor responsiveness with a cheap bounded call
match tokio::time::timeout(Duration::from_secs(2), client.get()).await {
    Ok(Ok(_)) => { /* actor healthy, proceed with prepare_replace */ }
    _ => anyhow::bail!("clash config actor unresponsive; not attempting prepare_replace"),
}

Try / catch

match client.replace_if_version(expected_version, next).await {
    Err(e) if e.to_string().contains("timed out") => {
        // bounded retry with backoff; do not retry unbounded
        tokio::time::sleep(Duration::from_secs(1)).await;
        client.replace_if_version(expected_version, next).await
    }
    other => other,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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/6821b8568ba98882. Report an issue: GitHub.