tursodatabase/turso · critical
spill_append_frames_to_wal requires a WAL
Error message
spill_append_frames_to_wal requires a WAL
What it means
Pager::spill_append_frames_to_wal() appends the prepared spill pages as WAL frames and requires self.wal with expect("spill_append_frames_to_wal requires a WAL"). Callers are supposed to route through the PreparingWal* states first, which initialize the WAL; reaching this function with no WAL attached breaks that ordering.
Source
Thrown at core/storage/pager.rs:4275
"try_spill_dirty_pages: successfully spilled {} pages to disk",
pages.len()
);
return Ok(IOResult::Done(()));
}
}
}
}
/// Append the prepared spill `pages` as WAL frames. Returns `Done` if
/// the write completed synchronously, otherwise transitions to
/// `SpillState::WritingToWal` and yields the write completion. The WAL
/// must already be initialized (callers route through `PreparingWal*`
/// first).
fn spill_append_frames_to_wal(&self, pages: Vec<PinGuard>) -> IOResultOr<()> {
let wal = self
.wal
.as_ref()
.expect("spill_append_frames_to_wal requires a WAL");
let page_sz = self.get_page_size().unwrap_or_default();
let wal_pages: Vec<PageRef> = pages
.iter()
.map(|p| -> Result<PageRef> {
self.subjournal_page_if_required(p)?;
// Set write_pending on all pages before WAL write so callback can
// detect mid-write modifications.
p.set_write_pending();
Ok(p.to_page())
})
.collect::<Result<Vec<_>>>()?;
let c = wal.append_frames_vectored(wal_pages, page_sz)?;
if c.succeeded() {
// Synchronous completion, WAL tags already set by callback.
{
let mut cache = self.page_cache.write();
for page in &pages {View on GitHub (pinned to 492c4a71cd)
Solutions
- Report to Turso with the workload - the spill state machine reached append without WAL init
- Avoid overwhelming the page cache on WAL-less (e.g. in-memory) databases: raise cache size or batch commits
- Disable cache spill on such databases (pager.set_spill_enabled(false))
- Ensure the connection is not being closed concurrently with a long write statement
Defensive patterns
Strategy: validation
Validate before calling
// Same guard as the spill-entry point: ensure WAL is in play or spill is off // before heavy writes on WAL-less databases: pager.set_spill_enabled(false); // embedder code for :memory: databases // Application level: keep single-statement write volume under the cache size, // or use journal_mode=WAL for file databases.
Try / catch
let result = std::panic::catch_unwind(AssertUnwindSafe(|| do_big_insert(&conn)));
if result.is_err() { conn.close().ok(); /* reopen, split the write into batches */ } Prevention
- Avoid overwhelming the page cache on in-memory/WAL-less databases
- Do not close connections concurrently with long-running write statements (WAL teardown can race spill)
- Batch large INSERT/UPDATE statements with intermediate commits
- Report spill-state-machine ordering panics upstream
When it happens
Trigger: A cache spill that skips or fails through the PreparingWalStart/PreparingWalFinish initialization states and lands directly in frame append on a pager whose WAL is absent (in-memory database, WAL-less configuration) or was torn down mid-spill.
Common situations: Memory databases under heavy write load, engine regressions in spill state ordering, WAL teardown racing a spill (close during spill).
Related errors
- PreparingWalStart requires a WAL
- DB should not be initialized and should not do any IO
- result should be set
- buffer not loaded
- invalid position to read current mvcc row
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/afb27e85718e4bae.
Report an issue: GitHub.