io: add platform-aware fsync helpers (dir + durable file sync)

Add an io::fsync module with a per-OS split (posix/windows) mirroring the
writeback_file convention, replacing two duplicated dir-fsync copies:

- dir(): POSIX directory fsync; a no-op on Windows, where std cannot open
  a directory as a File and the failed open logged a spurious warning on
  every mapfile write.
- file_durable(): opens the target read+write before sync_all so the flush
  succeeds on Windows, where FlushFileBuffers rejects a read-only handle
  with ERROR_ACCESS_DENIED.

Point the mapfile writer at the shared dir() helper.
This commit is contained in:
Matthew Jackson
2026-06-23 12:38:02 -07:00
parent 008c1f143e
commit d5afeb6088
5 changed files with 126 additions and 22 deletions
+2 -22
View File
@@ -630,32 +630,12 @@ impl Mapfile {
// this window is the wide one. Best-effort: a dir that can't be
// opened/synced (some filesystems, Windows) is not a write failure.
if let Some(parent) = self.path.parent() {
fsync_dir(parent);
crate::io::fsync::dir(parent);
}
Ok(())
}
}
/// fsync a directory so a prior `rename(2)` into it is durable. After a
/// crash a renamed file's dirent can otherwise be lost even though the
/// rename returned, because it is still page-cache-only. Best-effort:
/// opening the directory for read and `sync_all()`ing it is the POSIX way
/// to flush its metadata; failures (unsupported fs, Windows) are logged
/// and ignored rather than propagated, since the file bytes are already
/// durable and the caller's write succeeded.
fn fsync_dir(dir: &Path) {
match std::fs::File::open(dir) {
Ok(f) => {
if let Err(e) = f.sync_all() {
tracing::warn!(path = %dir.display(), error = %e, "failed to fsync mapfile directory");
}
}
Err(e) => {
tracing::warn!(path = %dir.display(), error = %e, "could not open mapfile directory to fsync");
}
}
}
impl Drop for Mapfile {
/// Best-effort flush on drop so a sweep / patch that returns early
/// (or unwinds) doesn't lose its in-memory state. Errors here are
@@ -863,7 +843,7 @@ mod tests {
// The directly-called dir fsync helper must be a no-op-on-error,
// never a panic, even for a nonexistent directory.
fsync_dir(&dir.join("does-not-exist"));
crate::io::fsync::dir(&dir.join("does-not-exist"));
let loaded = Mapfile::load(&p).unwrap();
assert_eq!(loaded.entries(), mf.entries());