databendlabs/databend · error · BrokenPipe
memory pool is closed
Error message
memory pool is closed
What it means
A sentinel error raised in AsyncBuffer::alloc_buffer when recv_blocking on the available_buffers channel fails, meaning the memory pool has been shut down while a caller was waiting for a buffer to become available. It fires during spill buffering after close/drop of the pool, signaling that no further allocations can be served.
Solutions
- Check the pool's shutdown/close state before attempting to allocate, and stop spilling work when the SpillLimiter or owning pool has been closed
- Propagate the closure as a termination signal: cancel in-flight spill tasks and drain any pending alloc_buffer waiters instead of letting them error
- Acquire the buffer before beginning a spill operation so closure is detected up front and partial writes are avoided
- Wrap allocation in retry logic that first verifies pool liveness, returning a clean cancellation error rather than an io::Error on close
Defensive patterns
Strategy: fallback
When it happens
Trigger: Thrown at src/query/service/src/spillers/async_buffer.rs:128 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/bdb19442c7cced7d.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/service/src/spillers/async_buffer.rs:128
})
}
fn alloc_buffer(&self) -> io::Result<BytesMut> {
if let Ok(buf) = self.available_buffers.try_recv() {
return Ok(buf);
}
let allocated = self.allocated_chunks.fetch_add(1, Ordering::AcqRel);
if allocated < self.max_chunks {
return Ok(BytesMut::with_capacity(CHUNK_SIZE));
}
self.allocated_chunks.fetch_sub(1, Ordering::AcqRel);
let start = Instant::now();
let result = match self.available_buffers.recv_blocking() {
Ok(buf) => Ok(buf),
Err(_) => Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"memory pool is closed",
)),
};
self.blocking_nanos
.fetch_add(start.elapsed().as_nanos() as u64, Ordering::Relaxed);
self.blocking_count.fetch_add(1, Ordering::Relaxed);
result
}
fn release_buffer(&self, buffer: BytesMut) {
if self.available_buffers_tx.try_send(buffer).is_err() {
unreachable!("MemoryPool available_buffers need unbounded.");
}
}
}
impl Drop for MemoryPool {View on GitHub (pinned to 288d84d76e)