{"record":{"id":"81cc8fb2cdeb1901","repo":"databendlabs/databend","slug":"async-crash-me-panic","errorCode":null,"errorMessage":"async crash me panic","messagePattern":"async crash me panic","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/query/service/src/table_functions/async_crash_me.rs","lineNumber":155,"sourceCode":"    pub fn create(\n        ctx: Arc<dyn TableContext>,\n        output: Arc<OutputPort>,\n        message: Option<String>,\n    ) -> Result<ProcessorPtr> {\n        AsyncSourcer::create(ctx.get_scan_progress(), output, AsyncCrashMeSource {\n            message,\n        })\n    }\n}\n\n#[async_trait::async_trait]\nimpl AsyncSource for AsyncCrashMeSource {\n    const NAME: &'static str = \"async_crash_me\";\n\n    #[async_backtrace::framed]\n    async fn generate(&mut self) -> Result<Option<DataBlock>> {\n        match &self.message {\n            None => panic!(\"async crash me panic\"),\n            Some(message) => panic!(\"{}\", message),\n        }\n    }\n}\n\nimpl TableFunction for AsyncCrashMeTable {\n    fn function_name(&self) -> &str {\n        self.name()\n    }\n\n    fn as_table<'a>(self: Arc<Self>) -> Arc<dyn Table + 'a>\n    where Self: 'a {\n        self\n    }\n}\n","sourceCodeStart":137,"sourceCodeEnd":171,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/query/service/src/table_functions/async_crash_me.rs#L137-L171","documentation":"alloc_buffer waits on a channel of pre-allocated buffers from the spill memory pool. If the channel's sender is dropped (the pool has been shut down) recv_blocking fails and this io::Error (BrokenPipe) is returned instead of a buffer. It signals use of the spill buffer pool after its closure, not transient backpressure.","triggerScenarios":"Calling alloc_buffer on an AsyncBuffer/spill pool that has already been closed; a race where the pool is dropped while a worker is still allocating.","commonSituations":"Query cancellation/shutdown tearing down the memory pool while spill workers are mid-flight; operator restart logic attempting to allocate from a dead pool; tests dropping the pool early.","solutions":["Ensure the pool outlives all workers: shut down spill workers before closing the pool.","Treat the error as terminal — stop the spill operation rather than retrying allocation.","Guard the allocation path with a pool-closed state check before calling alloc_buffer.","If seen during normal (non-shutdown) operation, audit the pool's drop/close path for premature closure."],"exampleFix":"// before\nlet buf = pool.alloc_buffer().await?;\n// after\nif pool.is_closed() { return Ok(None); } // graceful stop on shutdown\nlet buf = pool.alloc_buffer().await.map_err(|e| if is_pool_closed(&e) { SpillError::Stopped } else { e.into() })?;","handlingStrategy":"try-catch","validationCode":"if pool_closed.load(Ordering::Acquire) { return Err(SpillError::PoolClosed); }","typeGuard":"fn pool_alive(pool: &AsyncBuffer) -> bool { !pool.is_closed() }","tryCatchPattern":"match alloc_buffer(&pool).await { Err(e) if e.kind() == io::ErrorKind::BrokenPipe && pool.is_closed() => Ok(CancelSpill), Err(e) => Err(e.into()), Ok(buf) => Ok(Use(buf)) }","preventionTips":["Join/close all spill workers before dropping the pool","Expose and check an is_closed flag before allocating","Never retry allocation after this error — it is terminal","Cover shutdown-during-spill in integration tests"],"tags":["rust","spill","memory-pool","shutdown"],"backgroundTag":"broken-pipe","analyzedSha":"288d84d76e20a2f8f7173bda9691eb6ece301aa9","analyzedAt":"2026-09-11T11:29:36.208Z","contentChangedAt":"2026-09-11T11:29:36.208Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}