astrid-runtime/astrid · error · io::Error

vectored frame append made no progress

Error message

vectored frame append made no progress

What it means

This WriteZero error is raised by write_all_vectored when a vectored (scatter/gather) write of prepared durable frames reports 0 bytes written. A vectored write that returns Ok(0) means no progress was made, which would otherwise cause an infinite retry loop in the caller. The library treats zero-progress appends as an I/O fault and aborts the frame-batch append.

Solutions

  1. Check disk space, quota, and inode availability on the storage device holding the durable log
  2. Retry the append operation; transient zero-progress writes often clear once the device recovers
  3. If wrapping the writer, fix write_vectored to return Err(WouldBlock) or a nonzero count instead of Ok(0)
  4. Verify the target file handle is still open and valid; reopen the journal if the handle went stale

Example fix

// before
writer.write_vectored(&bufs)?; // returns Ok(0) silently
// after
let n = writer.write_vectored(&bufs)?;
if n == 0 {
    return Err(io::Error::new(io::ErrorKind::WriteZero, "no progress"));
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before appending
let avail = fs4::available_space(log_path)?;
if avail < required_batch_bytes { return Err("insufficient disk space".into()); }

Try / catch

match result {
    Err(e) if e.kind() == io::ErrorKind::WriteZero => {
        // check disk space / handle validity, then retry append
        eprintln!("vectored append made no progress: {e}");
    }
    ...
}

Prevention

When it happens

Trigger: Calling append_prepared_frames (which routes through write_all_vectored) when the underlying writer accepts zero bytes, e.g. writing to a closed/full pipe, a file at a hard resource limit, or a writer whose write_vectored implementation spuriously returns Ok(0). vectored_append_retries_interrupted_and_short_writes surfaces it after retries are exhausted.

Common situations: Disk full or quota exceeded on the journal device; running inside a container with a frozen filesystem; a custom/wrapped writer with a buggy write_vectored that returns 0 instead of WouldBlock; EINTR-style interruptions the retry loop could not recover from.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/dffbfd29942a8e23. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-storage/src/engine/durable/format/prepared.rs:115

fn write_all_vectored<W: Write + ?Sized>(
    writer: &mut W,
    slices: &mut [IoSlice<'_>],
) -> Result<(), DurableError> {
    let mut remaining = slices;
    while !remaining.is_empty() {
        let written = loop {
            match writer.write_vectored(remaining) {
                Ok(written) => break written,
                Err(source) if source.kind() == io::ErrorKind::Interrupted => {},
                Err(source) => {
                    return Err(io_error("append prepared durable frame batch", source));
                },
            }
        };
        if written == 0 {
            return Err(io_error(
                "append prepared durable frame batch",
                io::Error::new(
                    io::ErrorKind::WriteZero,
                    "vectored frame append made no progress",
                ),
            ));
        }
        IoSlice::advance_slices(&mut remaining, written);
    }
    Ok(())
}

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

    use super::*;
    use crate::engine::durable::append_frames;

View on GitHub (pinned to affd8760f4)