Skip to content

Commit 150b5b4

Browse files
authored
snapshot: Don't fsync directories on Windows (#4939)
One shall not fsync a directory on Windows, or else receive an unhelpful access denied error. Caught by the Windows smoketests for #4892 (failure to create a snapshot wouldn't fail the smoketests, perhaps they should in the future).
1 parent dd1b66b commit 150b5b4

1 file changed

Lines changed: 30 additions & 10 deletions

File tree

crates/snapshot/src/lib.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,22 +234,16 @@ struct UnflushedSnapshotInner {
234234

235235
impl UnflushedSnapshotInner {
236236
fn sync_all(self) -> Result<SnapshotDirPath, SnapshotError> {
237-
fn fsync(path: &Path) -> io::Result<()> {
238-
File::open(path)
239-
.and_then(|fd| fd.sync_all())
240-
.map_err(|e| io::Error::new(e.kind(), format!("failed to fsync {}: {}", path.display(), e)))
241-
}
242-
243237
// Sync all objects and their parent directories.
244238
// The paths yielded by the [Snapshot::files] iterator are constructed
245239
// by [DirTree::file_path], which creates a path with a parent.
246240
// `parent()` is thus known to succeed.
247241
for (_, path) in self.snapshot.files(&self.object_repo) {
248-
fsync(&path)?;
249-
fsync(path.parent().unwrap())?;
242+
FileOrDirPath::File(&path).sync_all()?;
243+
FileOrDirPath::Dir(path.parent().unwrap()).sync_all()?;
250244
}
251245
// Sync the root directory of the object repo
252-
fsync(self.object_repo.root())?;
246+
FileOrDirPath::Dir(self.object_repo.root()).sync_all()?;
253247
// Write out the snapshot file (syncs internally).
254248
self.snapshot_repo
255249
.write_snapshot_file(&self.snapshot_dir, self.snapshot)?;
@@ -855,7 +849,7 @@ impl SnapshotRepository {
855849
.into_inner()
856850
.expect("buffered writer just flushed")
857851
.sync_all()?;
858-
File::open(&snapshot_dir.0)?.sync_all()?;
852+
FileOrDirPath::Dir(&snapshot_dir.0).sync_all()?;
859853
}
860854

861855
Ok(())
@@ -1363,6 +1357,32 @@ pub struct ReconstructedSnapshot {
13631357
pub compress_type: CompressType,
13641358
}
13651359

1360+
/// A [Path] statically known to point to either a file or a directory.
1361+
enum FileOrDirPath<'a> {
1362+
File(&'a Path),
1363+
Dir(&'a Path),
1364+
}
1365+
1366+
impl FileOrDirPath<'_> {
1367+
/// `fsync` the file or directory at path `self`.
1368+
///
1369+
/// On *nix systems, both a file and its enclosing directory should be
1370+
/// `fsync`ed to make the file durable.
1371+
///
1372+
/// On Windows, only the file needs to be synced, and it's even an error to
1373+
/// sync a directory. Passing in [Self::Dir] is thus a no-op on Windows.
1374+
fn sync_all(&self) -> io::Result<()> {
1375+
#[cfg(target_os = "windows")]
1376+
if let Self::Dir(_) = self {
1377+
return Ok(());
1378+
}
1379+
let (Self::File(path) | Self::Dir(path)) = self;
1380+
File::open(path)
1381+
.and_then(|fd| fd.sync_all())
1382+
.map_err(|e| io::Error::new(e.kind(), format!("failed to fsync {}: {}", path.display(), e)))
1383+
}
1384+
}
1385+
13661386
#[cfg(test)]
13671387
mod tests {
13681388
use std::fs::OpenOptions;

0 commit comments

Comments
 (0)