databendlabs/databend · critical
current_page has taken
Error message
current_page has taken
What it means
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.
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.
Example fix
// before
let page_builder = self.current_page.as_mut().expect("current_page has taken");
// after
let page_builder = match self.current_page.as_mut() {
Some(p) => p,
None => return Err(SendFail::Closed), // page handed off; no new page installed
}; Defensive patterns
Strategy: try-catch
Type guard
fn has_current_page(q: &SizedSpsc) -> bool { q.current_page.is_some() } Try / catch
// Producer-side: treat missing page as closed channel instead of panicking
let page_builder = match self.current_page.as_mut() {
Some(p) => p,
None => return Err(SendFail::Closed),
}; Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/e958bdd84e45f98b.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/service/src/servers/http/v1/query/sized_spsc.rs:111
})
.sum()
}
fn has_page_ready(&self) -> bool {
!self.pages.is_empty()
}
fn is_pages_full(&self, reserve: usize) -> bool {
self.pages_rows() + reserve > self.max_rows
}
fn try_add_block(&mut self, mut block: DataBlock) -> result::Result<(), SendFail> {
if self.is_recv_stopped || self.is_send_stopped {
return Err(SendFail::Closed);
}
loop {
let page_builder = self.current_page.as_mut().expect("current_page has taken");
let remain = page_builder.try_append_block(block);
if !page_builder.has_capacity() {
let rows = page_builder.num_rows();
if self.is_pages_full(rows) {
return Err(SendFail::Full {
page: self
.current_page
.take()
.expect("current_page has taken")
.into_page(),
remain,
});
}
let page = self
.current_page
.replace(PageBuilder::new(self.page_rows))
.expect("current_page has taken")View on GitHub (pinned to 288d84d76e)