clockworklabs/SpacetimeDB · error · io::Error

failed to open file for fsync

Error message

failed to open file {} for fsync: {}

What it means

For Self::File(path), crates/snapshot's sync_all opens the file with explicit options (read+write, no truncate/create/append — write access is required because Windows needs it for sync_all). If the open fails, the underlying kind is preserved and the error is wrapped as 'failed to open file {path} for fsync: {source}'. The file is expected to already exist; the sync helper never creates it.

Solutions

  1. Verify the file exists and check its permissions before syncing; re-run the snapshot write if it was never created
  2. On Windows, clear read-only attributes / coordinate with scanners; open with appropriate sharing
  3. Fix umask/ownership so snapshot files are read-write for the syncing process
  4. Match ErrorKind::NotFound to distinguish 'nothing to sync' from real I/O failure

Example fix

// before
SyncTarget::File(&file).sync_all()?; // NotFound: file already GC'd

// after
if !file.is_file() {
    anyhow::bail!("snapshot file {} vanished before fsync", file.display());
}
std::fs::set_permissions(&file, std::fs::Permissions::from_mode(0o644))?; // if umask was the issue
SyncTarget::File(&file).sync_all()?;
Defensive patterns

Strategy: validation

Validate before calling

if !file.is_file() {
    anyhow::bail!("cannot fsync {}: not present", file.display());
}
#[cfg(unix)]
std::fs::set_permissions(&file, std::fs::Permissions::from_mode(0o644))?;

Type guard

fn is_syncable_file(p: &Path) -> bool { p.is_file() }

Try / catch

if let Err(e) = SyncTarget::File(&file).sync_all() {
    if e.kind() == std::io::ErrorKind::NotFound { /* vanished: re-run snapshot write or skip */ }
    else if e.kind() == std::io::ErrorKind::PermissionDenied { /* ownership/umask: fix and retry once */ }
    else { return Err(e); }
}

Prevention

When it happens

Trigger: Syncing a snapshot file that was deleted or moved before the sync ran (NotFound); insufficient permissions to open read-write (PermissionDenied); on Windows, read-only attributes or sharing violations; a race where another process holds an exclusive lock.

Common situations: Concurrent cleanup or antivirus scanning removing/locking snapshot files on Windows; file written by a different user/umask so the snapshot process cannot open it writable; orchestration code syncing a path that was never written due to an earlier silent skip.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20). Data as JSON: /api/errors/861dd70a18f24e70. Report an issue: GitHub.

Appendix: source

Thrown at crates/snapshot/src/lib.rs:1648

                    io::Error::new(
                        e.kind(),
                        format!("failed to open directory {} for fsync: {}", path.display(), e),
                    )
                })?
                .sync_all()
                .map_err(|e| io::Error::new(e.kind(), format!("failed to fsync directory {}: {}", path.display(), e))),
            Self::File(path) => {
                File::options()
                    .read(true)
                    // Windows needs the file to be writable for `sync_all` to work.
                    // Set all the open options explicitly, just for visibility.
                    .write(true)
                    .truncate(false)
                    .create(false)
                    .append(false)
                    .open(path)
                    .map_err(|e| {
                        io::Error::new(
                            e.kind(),
                            format!("failed to open file {} for fsync: {}", path.display(), e),
                        )
                    })?
                    .sync_all()
                    .map_err(|e| io::Error::new(e.kind(), format!("failed to fsync file {}: {}", path.display(), e)))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::fs::OpenOptions;

    use tempfile::tempdir;

    use super::*;

View on GitHub (pinned to 9e0d92412f)