clockworklabs/SpacetimeDB · error · std::io::Error
no space left on device
Error message
no space left on device
What it means
The memory repo emulates a storage device of fixed capacity: Memory::new(total_space) creates a SpaceOnDevice budget shared by all segments, and segments allocate space in PAGE_SIZE (4096-byte) increments. When a write, ftruncate, or fallocate needs more pages than remain, the allocation returns io::ErrorKind::StorageFull with the POSIX ENOSPC message.
Source
Thrown at crates/commitlog/src/repo/mem/segment.rs:252
if *avail == 0 {
return Err(enospc());
}
let want = size.next_multiple_of(PAGE_SIZE as u64) - storage.alloc;
let have = want.min(*avail);
storage.alloc += have;
*avail -= have;
if want > have {
return Err(enospc());
}
Ok(())
}
}
fn enospc() -> io::Error {
io::Error::new(io::ErrorKind::StorageFull, "no space left on device")
}
#[cfg(feature = "streaming")]
mod async_impls {
use super::*;
use std::{
io::{Read as _, Seek as _, Write as _},
pin::Pin,
task::{Context, Poll},
};
use tokio::io::{self, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
use crate::stream::{AsyncFsync, AsyncLen, IntoAsyncWriter};
impl IntoAsyncWriter for Segment {
type AsyncWriter = tokio::io::BufWriter<Self>;View on GitHub (pinned to 6dee26c6ef)
Solutions
- Raise the total_space passed to Memory::new, or use Memory::unlimited() when capacity simulation is not the point of the test
- Size the budget for worst case: at least max_segment_size per live segment plus overhead
- Remove or compact old segments to release their pages before creating new ones
- Catch ErrorKind::StorageFull and treat it as a backpressure signal rather than a hard failure
Example fix
// before let repo = Memory::new(4096); // one page only // after // budget for N segments of max_segment_size each let repo = Memory::new(options.max_segment_size.get() * n_segments as u64 + PAGE_SIZE as u64);
Defensive patterns
Strategy: try-catch
Validate before calling
// If you constructed the Memory repo yourself, track the budget:
// space is Arc<Mutex<u64>>; needed pages = ceil(bytes / PAGE_SIZE)
let pages_needed = (bytes + PAGE_SIZE - 1) / PAGE_SIZE;
if *space.lock().unwrap() < pages_needed as u64 * PAGE_SIZE as u64 {
return Err(io::Error::new(io::ErrorKind::StorageFull, "insufficient budget"));
} Type guard
fn is_storage_full(err: &io::Error) -> bool {
err.kind() == io::ErrorKind::StorageFull
} Try / catch
match segment.write_all(&buf) {
Ok(()) => Ok(()),
Err(e) if is_storage_full(&e) => {
// release capacity (remove/compact old segments) or apply backpressure
handle_full_device()?;
segment.write_all(&buf)
}
Err(e) => Err(e),
} Prevention
- Budget total_space as max_segment_size * max_live_segments + one page of slack
- Use Memory::unlimited() in tests that do not specifically test capacity behavior
- Treat StorageFull as backpressure: pause writers and free segments instead of crashing
When it happens
Trigger: Constructing Memory::new with a small total_space in tests and writing more data than it allows; fallocating Options::max_segment_size for several segments until the budget is exhausted; ftruncate extending a segment beyond available space (see mem.rs tests: ftruncate to 9216 on a 2-page budget fails).
Common situations: Tests that intentionally set a tiny device budget to assert backpressure; forgetting that fallocate/preallocation reserves capacity per segment; sizing total_space below max_segment_size times the expected segment count.
Related errors
- segment {offset} already exists
- segment {offset} does not exist
- invalid seek to a negative or overflowing position
- no space left on device
- failed to read {} bytes of commit payload: {}
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/4c745d500c127cd8.
Report an issue: GitHub.