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
- Verify the file exists and check its permissions before syncing; re-run the snapshot write if it was never created
- On Windows, clear read-only attributes / coordinate with scanners; open with appropriate sharing
- Fix umask/ownership so snapshot files are read-write for the syncing process
- 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
- Write snapshot files with permissive-enough umask for the syncing process
- On Windows, account for AV/scanners holding handles exclusively
- Verify file presence right before the sync step in recovery flows
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
- failed to fsync directory
- failed to fsync file
- failed to open directory
- `close` was called on this `NoDurability` instance
- error syncing data to disk
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)