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

  1. Always acquire pages through pager.read_page() / return_if_locked_maybe_load! so the buffer is loaded before contents are touched
  2. Check page.is_loaded() before accessing contents in any custom code
  3. Treat occurrences inside engine paths as bugs - report them with a reproduction
  4. 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

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


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/409a282e90d395e0. Report an issue: GitHub.