{"record":{"id":"e958bdd84e45f98b","repo":"databendlabs/databend","slug":"current-page-has-taken","errorCode":null,"errorMessage":"current_page has taken","messagePattern":"current_page has taken","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/query/service/src/servers/http/v1/query/sized_spsc.rs","lineNumber":111,"sourceCode":"            })\n            .sum()\n    }\n\n    fn has_page_ready(&self) -> bool {\n        !self.pages.is_empty()\n    }\n\n    fn is_pages_full(&self, reserve: usize) -> bool {\n        self.pages_rows() + reserve > self.max_rows\n    }\n\n    fn try_add_block(&mut self, mut block: DataBlock) -> result::Result<(), SendFail> {\n        if self.is_recv_stopped || self.is_send_stopped {\n            return Err(SendFail::Closed);\n        }\n\n        loop {\n            let page_builder = self.current_page.as_mut().expect(\"current_page has taken\");\n\n            let remain = page_builder.try_append_block(block);\n            if !page_builder.has_capacity() {\n                let rows = page_builder.num_rows();\n                if self.is_pages_full(rows) {\n                    return Err(SendFail::Full {\n                        page: self\n                            .current_page\n                            .take()\n                            .expect(\"current_page has taken\")\n                            .into_page(),\n                        remain,\n                    });\n                }\n                let page = self\n                    .current_page\n                    .replace(PageBuilder::new(self.page_rows))\n                    .expect(\"current_page has taken\")","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/query/service/src/servers/http/v1/query/sized_spsc.rs#L93-L129","documentation":"In `sized_spsc` (the sized single-producer/single-consumer block channel used by HTTP query result streaming), `try_add_block` assumes `self.current_page` is always `Some` and unwraps it with `.expect(\"current_page has taken\")`. The page is only moved out (taken) when the producer transitions a full page to the consumer; if the producer then appends another block, the invariant is broken. This signals a producer-side state machine bug or race where blocks are added after the page hand-off without installing a new page.","triggerScenarios":"Calling `try_add_block` after `current_page` was taken (page handed to the receiver) and before/without a replacement page being created — e.g., blocks pushed after `is_pages_full` handling raced ahead, or after the receiver set stop flags in a window the guard didn't cover.","commonSituations":"HTTP query clients reading results while the producer keeps streaming; extremely fast consumers causing frequent page hand-offs that expose the race; dev/regression builds where page-swap logic in the send path was refactored.","solutions":["Reproduce with the query whose result stream panics and check whether it stops sending after the consumer finished; ensure the producer checks send/recv stop flags before each append.","Fix `try_add_block` to re-create or return `SendFail::Closed` when `current_page` is `None` instead of unwrapping.","Audit the page hand-off code path (`is_pages_full` → page swap) for a missing `current_page = Some(new_page)` assignment.","Upgrade Databend if this arises during normal HTTP query streaming, as it indicates a fixed channel regression."],"exampleFix":"// before\nlet page_builder = self.current_page.as_mut().expect(\"current_page has taken\");\n\n// after\nlet page_builder = match self.current_page.as_mut() {\n    Some(p) => p,\n    None => return Err(SendFail::Closed), // page handed off; no new page installed\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn has_current_page(q: &SizedSpsc) -> bool { q.current_page.is_some() }","tryCatchPattern":"// Producer-side: treat missing page as closed channel instead of panicking\nlet page_builder = match self.current_page.as_mut() {\n    Some(p) => p,\n    None => return Err(SendFail::Closed),\n};","preventionTips":["Ensure consumers signal stop before/through the channel so producers stop appending, never race past page hand-off.","When refactoring page-swap logic, assert current_page is restored to Some immediately after hand-off.","Add stress tests that stream results to fast consumers over the HTTP API.","Track the query id in panics via panic hooks to correlate channel state bugs quickly."],"tags":["concurrency","channel","panic","http-api","rust"],"backgroundTag":"internal-invariant-violation","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"}