libnyanpasu/clash-nyanpasu · error

Clash stream actor timed out

Error message

Clash stream actor timed out

What it means

The Clash websocket/IPC bridge sends a typed message to a ractor actor with a 10-second RPC timeout and maps CallResult::Timeout to this error. It means the Clash stream actor received the request but did not reply within 10 seconds — the actor is busy, blocked, or its handler hung. Distinguish it from 'actor unavailable' (send failure) and 'reply dropped' (sender error).

Solutions

  1. Check the Clash core process is alive and the websocket endpoint is responsive; restart the core if hung
  2. Increase the 10s timeout only if the operation is legitimately slow — prefer diagnosing the stall first
  3. Inspect the actor's message loop for blocking calls or cross-actor cycles that can deadlock (avoid StateActor -> CoreActor -> StateActor patterns)
  4. Add retry-with-backoff at the caller for idempotent messages like Start

Example fix

// before
.actor.call(message, Some(Duration::from_secs(10)))
// after
match actor.call(message, Some(Duration::from_secs(10))).await {
    Ok(CallResult::Timeout) => log::warn!("clash stream actor timed out; retrying once"),
    other => return other.context("Clash stream actor unavailable"),
}
Defensive patterns

Strategy: retry

Validate before calling

// before calling: check core is reachable
// e.g. ping the clash controller endpoint with a short HTTP timeout

Try / catch

match client.start().await {
    Err(e) if e.to_string().contains("timed out") => retry_with_backoff(3, || client.start()),
    other => other,
}

Prevention

When it happens

Trigger: Calling start() (or other Message variants) on the clash ws client while the actor is stuck processing a prior message, the backend connection is stalled, or the actor task is starved on a blocked await for more than 10 seconds.

Common situations: Clash core hanging or slow to accept a websocket connection; network stalls to the core endpoint; actor deadlocked on a synchronous cross-actor call; heavy event loop under load.

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/2a5ee771a8192b06. Report an issue: GitHub.

Appendix: source

Thrown at backend/tauri/src/core/clash/ws.rs:695

        Ok(Self(Arc::new(Inner {
            actor,
            connections,
            events,
        })))
    }
    async fn call<T: Send + 'static>(
        &self,
        message: impl FnOnce(RpcReplyPort<T>) -> Message,
    ) -> Result<T> {
        match self
            .0
            .actor
            .call(message, Some(Duration::from_secs(10)))
            .await
            .context("Clash stream actor unavailable")?
        {
            CallResult::Success(value) => Ok(value),
            CallResult::Timeout => anyhow::bail!("Clash stream actor timed out"),
            CallResult::SenderError => anyhow::bail!("Clash stream actor reply dropped"),
        }
    }
    pub async fn start(&self) -> Result<()> {
        self.call(Message::Start).await
    }
    #[allow(dead_code)]
    pub async fn stop(&self) -> Result<()> {
        self.call(Message::Stop).await
    }
    pub async fn snapshot(&self) -> Result<ClashWsSnapshot> {
        self.call(Message::Snapshot).await
    }
    pub async fn set_recording(
        &self,
        kind: ClashWsKind,
        enabled: bool,
    ) -> Result<ClashWsRecording> {

View on GitHub (pinned to f7dbce2997)