clockworklabs/SpacetimeDB · error · io::Error

no space left on device

Error message

no space left on device

What it means

The in-memory commitlog models a device with a finite space budget: a shared u64 counter ('space') is decremented as segments grow their allocation. When Segment::write needs more pages than remain in the budget, it returns ErrorKind::StorageFull via enospc() with the classic ENOSPC message 'no space left on device'. This lets simulation tests exercise disk-full behavior deterministically (the test fixture at line ~470 constructs the repo with u64::MAX to disable the quota).

Source

Thrown at crates/dst/src/sim/commitlog.rs:463

        self.inner.seek(pos)
    }
}

impl SegmentLen for ReadOnlySegment {}

fn next_page_multiple(size: u64) -> io::Result<u64> {
    let page = PAGE_SIZE as u64;
    let remainder = size % page;
    if remainder == 0 {
        return Ok(size);
    }

    size.checked_add(page - remainder)
        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "allocation size overflow"))
}

fn enospc() -> io::Error {
    io::Error::new(io::ErrorKind::StorageFull, "no space left on device")
}

#[cfg(test)]
mod tests {
    use std::io::{Read, Seek, Write};

    use super::*;

    fn segment() -> Segment {
        Segment::from_shared(Arc::new(Mutex::new(u64::MAX)), Arc::new(RwLock::new(Storage::new())))
    }

    #[test]
    fn write_overwrites_at_seek_position() {
        let mut segment = segment();

        segment.write_all(b"abcdef").unwrap();
        segment.seek(io::SeekFrom::Start(2)).unwrap();

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Construct Memory with a larger space budget (u64::MAX disables the quota entirely) when disk-full is not the behavior under test
  2. Remove/rotate old segments (remove_segment frees their budget share) before writing new ones
  3. If testing the ENOSPC path intentionally, size the budget just below the write that should fail, and assert on ErrorKind::StorageFull

Example fix

// before
let repo = Memory::new(/* space */ 4096);
// ... writes beyond 4 KiB -> StorageFull

// after
let repo = Memory::new(u64::MAX); // quota disabled for this test
// or: remove_segment(old_offset)?; before appending
Defensive patterns

Strategy: fallback

Validate before calling

// If you expose the budget, pre-check headroom before a big append:
fn headroom(space: &Mutex<u64>, allocated: u64, want: u64) -> bool {
    *space.lock().unwrap() >= want.saturating_sub(allocated).next_multiple_of(4096)
}

Try / catch

match seg.write(buf) {
    Err(ref e) if e.kind() == std::io::ErrorKind::StorageFull => {
        repo.remove_segment(oldest_retired_offset)?; // free budget
        seg.write(buf)? // retry after freeing
    }
    r => r?,
}

Prevention

When it happens

Trigger: Creating a Memory repo with a finite space budget and writing more bytes than budgeted across segments; growing several segments concurrently so the shared counter is exhausted; tests that deliberately size the budget to trigger the full-disk path.

Common situations: Simulation harnesses for the commitlog/durable-log layer: quota set smaller than the log the test produces; forgetting to raise the budget when a test's data volume grows; retention not removing old segments so the budget drains.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/7f610d9f54fb425b. Report an issue: GitHub.