{"record":{"id":"f8e005de34cd9a20","repo":"zed-industries/zed","slug":"blocking-sender-returned-without-value","errorCode":null,"errorMessage":"blocking sender returned without value","messagePattern":"blocking sender returned without value","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gpui_linux/src/linux/dispatcher.rs","lineNumber":110,"sourceCode":"        Self {\n            main_sender,\n            timer_sender,\n            background_sender,\n            _background_threads: background_threads,\n            main_thread_id: thread::current().id(),\n        }\n    }\n}\n\nimpl PlatformDispatcher for LinuxDispatcher {\n    fn is_main_thread(&self) -> bool {\n        thread::current().id() == self.main_thread_id\n    }\n\n    fn dispatch(&self, runnable: RunnableVariant, priority: Priority) {\n        self.background_sender\n            .send(priority, runnable)\n            .unwrap_or_else(|_| panic!(\"blocking sender returned without value\"));\n    }\n\n    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {\n        self.main_sender\n            .send(priority, runnable)\n            .unwrap_or_else(|runnable| {\n                // NOTE: Runnable may wrap a Future that is !Send.\n                //\n                // This is usually safe because we only poll it on the main thread.\n                // However if the send fails, we know that:\n                // 1. main_receiver has been dropped (which implies the app is shutting down)\n                // 2. we are on a background thread.\n                // It is not safe to drop something !Send on the wrong thread, and\n                // the app will exit soon anyway, so we must forget the runnable.\n                std::mem::forget(runnable);\n            });\n    }\n","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/gpui_linux/src/linux/dispatcher.rs#L92-L128","documentation":"LinuxDispatcher::dispatch sends a runnable over a channel to the background executor. The send can only fail when the receiving side has been dropped, which happens while the dispatcher is being destroyed during app teardown; in that case it panics 'blocking sender returned without value'. The sibling dispatch_on_main_thread tolerates failure (main thread gone implies shutdown), so this panic indicates a task dispatched after the background executor was torn down.","triggerScenarios":"Any code on any thread calling dispatcher.dispatch after the GPUI app has begun quitting and the background receiver was dropped — typically detached tasks waking up during shutdown.","commonSituations":"Shutdown races in gpui_linux: background work spawned by GPUI or app code that outlives the dispatcher; late completion of I/O tasks during quit.","solutions":["Update Zed — dispatcher teardown ordering fixes land regularly; this panic is treated as an internal bug","In app code, don't detach tasks that can outlive the app; store and drop them before quit","Report with a backtrace and the exact quit sequence if reproducible"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Don't detach background tasks that can outlive the app; keep Task handles and drop them before quit","Check dispatcher liveness / app state before dispatching from long-lived background threads during shutdown","Update Zed — dispatcher teardown ordering is fixed upstream when reports come in"],"tags":["zed","gpui","linux","dispatcher","channel","shutdown","panic"],"backgroundTag":"send-on-closed-channel","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}