tursodatabase/turso · critical
buffer not loaded
Error message
buffer not loaded
What it means
Page::as_ptr() (core/storage/pager.rs) hands out a mutable slice of the page buffer and expects buffer to be Some ("buffer not loaded"). Pages whose buffer was taken during eviction or never loaded have buffer=None; touching their contents through get_contents()/as_ptr() panics.
Source
Thrown at core/storage/pager.rs:164
/// Creates a new PageInner with an owned buffer.
pub fn from_buffer(buffer: Buffer) -> Self {
Self {
flags: AtomicUsize::new(0),
id: 0,
pin_count: AtomicUsize::new(0),
wal_tag: AtomicU64::new(TAG_UNSET),
buffer: Some(Arc::new(buffer)),
overflow_cells: crate::alloc::vec![],
}
}
/// Get the page buffer as a mutable slice. Panics if buffer not loaded.
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn as_ptr(&self) -> &mut [u8] {
self.buffer
.as_ref()
.expect("buffer not loaded")
.as_mut_slice()
}
/// The position where page content starts. It's 100 for page 1 (database file header is 100 bytes),
/// 0 for all other pages.
#[inline]
pub fn offset(&self) -> usize {
if self.id == 1 {
DatabaseHeader::SIZE
} else {
0
}
}
/// Read a u8 from the page content at the given offset, taking account the possible db header on page 1.
#[inline]
fn read_u8(&self, pos: usize) -> u8 {
let buf = self.as_ptr();View on GitHub (pinned to 6c72522679)
Solutions
- Always acquire pages through pager.read_page() / return_if_locked_maybe_load! so the buffer is loaded before contents are touched
- Check page.is_loaded() before accessing contents in any custom code
- Treat occurrences inside engine paths as bugs - report them with a reproduction
- Reduce cache pressure (larger page cache) to avoid eviction windows
Example fix
// before
let contents = page.get_contents(); // panics if evicted: buffer is None
let ptr = contents.as_ptr();
// after
if !page.is_loaded() {
return Err(LimboError::InternalError(format!("page {} buffer not loaded", page.get().id)));
}
let contents = page.get_contents(); Defensive patterns
Strategy: validation
Validate before calling
// Before touching page contents in custom code over the pager/btree APIs:
if !page.is_loaded() {
return Err(LimboError::InternalError(format!(
"page {} buffer not loaded; re-read via pager.read_page()", page.get().id
)));
} Type guard
fn is_page_loaded(page: &PageRef) -> bool {
page.is_loaded()
} Prevention
- Acquire pages only through pager.read_page()/return_if_locked_maybe_load! which guarantee a loaded buffer
- Never cache PageRefs across operations that can evict (cache pressure, savepoint rollback)
- Check is_loaded() in debug assertions in custom storage code
- Report engine-path occurrences upstream - the commit path already guards this shape explicitly
When it happens
Trigger: Calling get_contents()/as_ptr() on a PageRef that was not (re)loaded - e.g. a page re-fetched from the cache after eviction without return_if_locked_maybe_load!, or a dirty page whose backing WAL frame was truncated by savepoint rollback.
Common situations: Custom btree/pager code skipping the load macros, heavy cache pressure evicting pages mid-operation, engine regressions in pin/reload discipline. The commit path already carries an explicit unloaded-page guard for this shape.
Related errors
- page evicted between scan and prepare
- DB should not be initialized and should not do any IO
- Page size too small, a ptrmap page cannot map any db pages.
- subjournal must be opened
- invalid page size stored
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20).
Data as JSON: /api/errors/409a282e90d395e0.
Report an issue: GitHub.