{"record":{"id":"2f3aa8c4aa3646c7","repo":"libnyanpasu/clash-nyanpasu","slug":"clash-stream-actor-reply-dropped","errorCode":null,"errorMessage":"Clash stream actor reply dropped","messagePattern":"Clash stream actor reply dropped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"backend/tauri/src/core/clash/ws.rs","lineNumber":696,"sourceCode":"            actor,\n            connections,\n            events,\n        })))\n    }\n    async fn call<T: Send + 'static>(\n        &self,\n        message: impl FnOnce(RpcReplyPort<T>) -> Message,\n    ) -> Result<T> {\n        match self\n            .0\n            .actor\n            .call(message, Some(Duration::from_secs(10)))\n            .await\n            .context(\"Clash stream actor unavailable\")?\n        {\n            CallResult::Success(value) => Ok(value),\n            CallResult::Timeout => anyhow::bail!(\"Clash stream actor timed out\"),\n            CallResult::SenderError => anyhow::bail!(\"Clash stream actor reply dropped\"),\n        }\n    }\n    pub async fn start(&self) -> Result<()> {\n        self.call(Message::Start).await\n    }\n    #[allow(dead_code)]\n    pub async fn stop(&self) -> Result<()> {\n        self.call(Message::Stop).await\n    }\n    pub async fn snapshot(&self) -> Result<ClashWsSnapshot> {\n        self.call(Message::Snapshot).await\n    }\n    pub async fn set_recording(\n        &self,\n        kind: ClashWsKind,\n        enabled: bool,\n    ) -> Result<ClashWsRecording> {\n        self.call(|reply| Message::Recording(kind, enabled, reply))","sourceCodeStart":678,"sourceCodeEnd":714,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/core/clash/ws.rs#L678-L714","documentation":"The ractor RPC returned CallResult::SenderError, meaning the reply channel's sender was gone when the actor tried to answer — the caller's oneshot receiver was dropped mid-request. In practice this indicates the request future was cancelled/dropped before the actor replied, or the actor framework failed to deliver the response. Distinct from timeout: the actor did (try to) reply, but nobody was listening.","triggerScenarios":"The future calling .actor.call(...) is aborted (task cancelled, request dropped, timeout wrapper cancelling the outer future) before the actor sends its reply; the reply port's receiver side was dropped.","commonSituations":"Tauri command handlers cancelled by the frontend (window closed / request aborted); tokio tasks aborted on shutdown; racing timeouts in an outer layer that drops the inner call future.","solutions":["Ensure callers await the call to completion or handle cancellation gracefully; avoid wrapping in select! that drops the call future mid-flight on timeouts","Log at debug level and surface as retryable; resend the message once for idempotent operations","Check actor reply code uses the correct RpcReplyPort and that State is not dropping ports early"],"exampleFix":"// before\nlet result = tokio::select! {\n    r = client.start() => r?,\n    _ = sleep(TIMEOUT) => bail!(\"outer timeout\"),\n};\n// after\nlet result = client.start().await?; // let the actor's own 10s RPC timeout govern cancellation","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.start().await {\n    Err(e) if e.to_string().contains(\"reply dropped\") => log::debug!(\"request cancelled before reply; safe to resend\"),\n    other => other?,\n}","preventionTips":["Don't drop/abort the call future mid-flight (avoid select! cancellation of actor RPCs)","Await actor calls to completion or implement explicit resend for idempotent messages"],"tags":["actor","rust","cancellation"],"backgroundTag":"broken-pipe","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}