EpicGames/lore · error · io::Error (WriteZero)

file refused further writes

Error message

file refused further writes

What it means

The io_uring write_all_at loop treats a completed write of 0 bytes as "the file refused further writes" and fails with ErrorKind::WriteZero. This preserves all-or-nothing semantics: either the whole buffer is written or the caller gets an error, never a silent partial write.

Solutions

  1. Free disk space / raise quota, then retry the write from a known offset.
  2. Reopen the file and restart the write; a 0-byte write mid-loop means prior bytes may be durable, so rewrite the whole buffer if idempotence matters.
  3. Stat available space before large writes and stream in chunks with ENOSPC handling.
  4. Ensure the file is opened with write/append permissions and the offset is within a supportable range.

Example fix

// before
file.write_all_at(&data, offset).await?; // WriteZero on full disk
// after
if free_bytes(&path)? < data.len() as u64 {
    return Err(std::io::Error::new(std::io::ErrorKind::StorageFull, "volume full"));
}
file.write_all_at(&data, offset).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust
if fs2::available_space(&path)? < data.len() as u64 {
    return Err(std::io::Error::new(std::io::ErrorKind::StorageFull, "volume full before async write"));
}

Try / catch

// Rust
match file.write_all_at(&data, offset).await {
    Ok(()) => {}
    Err(e) if e.kind() == std::io::ErrorKind::WriteZero => { /* ENOSPC: free space, reopen, rewrite whole buffer */ }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling write_all_at when an io_uring write completes with 0 bytes: full volume/quota, file truncated concurrently, write at an offset the filesystem cannot extend, or a special-file target that stopped accepting data.

Common situations: Disk-full during a large async write; ENOSPC surfacing as a zero-length completion; concurrent truncation by another task/process; container disk limits hit mid-write.

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 EpicGames/lore@074eb0b0d1 (2026-09-13). Data as JSON: /api/errors/cb663d0f9f0a9580. Report an issue: GitHub.

Appendix: source

Thrown at lore-io/src/uring.rs:405

        len: usize,
        offset: u64,
    ) -> std::io::Result<B> {
        let mut buffer = buffer;
        let mut done = 0;
        while done < len {
            let entry = write_entry(
                &file,
                buffer.as_ref(),
                done,
                len - done,
                offset + done as u64,
            );
            let (payload, result) = self.submit(entry, payload(buffer, &file))?.await;
            buffer = payload.buffer;
            match interpret(result)? {
                Progress::Interrupted => {}
                Progress::Bytes(0) => {
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::WriteZero,
                        "file refused further writes",
                    ));
                }
                Progress::Bytes(written) => done += written,
            }
        }
        Ok(buffer)
    }

    /// Scatters one read across the segments, returning how many bytes it filled.
    ///
    /// The iovec array is rebuilt for every pass because it travels into the op entry with the
    /// segments, and a pass that made partial progress needs a different array anyway: the skip
    /// moves and the leading segments drop out.
    pub(crate) async fn read_vectored_at<B: StableBufListMut>(
        &self,
        file: Arc<File>,

View on GitHub (pinned to 074eb0b0d1)