platform: use F_FULLSYNC on macOS for SyncFile data durability, fixes #9383

This commit is contained in:
Mrityunjay Raj 2026-02-20 22:39:07 +05:30
parent 9533b5083e
commit 0354697c56
4 changed files with 94 additions and 1 deletions

View file

@ -50,6 +50,7 @@ elif is_darwin: # pragma: darwin only
from .darwin import acl_get, acl_set
from .darwin import is_darwin_feature_64_bit_inode, _get_birthtime_ns
from .darwin import set_flags
from .darwin import fdatasync, sync_dir # type: ignore[no-redef]
from .base import get_flags
from .base import SyncFile
from .posix import process_alive, local_pid_alive

View file

@ -151,7 +151,6 @@ class SyncFile:
Calling SyncFile(path) for an existing path will raise FileExistsError. See the comment in __init__.
TODO: Use F_FULLSYNC on macOS.
TODO: A Windows implementation should use CreateFile with FILE_FLAG_WRITE_THROUGH.
"""

View file

@ -262,3 +262,33 @@ def set_flags(path, bsd_flags, fd=None):
path_bytes = os.fsencode(path)
if lchflags(path_bytes, c_flags) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path_bytes))
import errno as errno_mod
import fcntl as fcntl_mod
def fdatasync(fd):
"""macOS fdatasync using F_FULLFSYNC for true data durability.
On macOS, os.fsync() only flushes to the drive's write cache.
fcntl F_FULLFSYNC flushes to persistent storage.
Falls back to os.fsync() if F_FULLFSYNC is not supported.
"""
try:
fcntl_mod.fcntl(fd, fcntl_mod.F_FULLFSYNC)
except OSError:
# F_FULLFSYNC not supported (e.g. network filesystem), fall back
os.fsync(fd)
def sync_dir(path):
"""Sync a directory to persistent storage on macOS using F_FULLFSYNC."""
fd = os.open(str(path), os.O_RDONLY)
try:
fdatasync(fd)
except OSError as os_error:
if os_error.errno != errno_mod.EINVAL:
raise
finally:
os.close(fd)

View file

@ -2,6 +2,7 @@ import os
import tempfile
from ...platform import acl_get, acl_set
from ...platform import fdatasync, sync_dir
from .platform_test import skipif_not_darwin, skipif_fakeroot_detected, skipif_acls_not_working
# Set module-level skips
@ -46,3 +47,65 @@ def test_extended_acl():
b"group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000000::0:allow:read"
in get_acl(file2.name, numeric_ids=True)["acl_extended"]
)
def test_fdatasync_uses_f_fullfsync(monkeypatch):
"""Verify fcntl F_FULLFSYNC is called."""
import fcntl as fcntl_mod
from ...platform import darwin
calls = []
original_fcntl = fcntl_mod.fcntl
def mock_fcntl(fd, cmd, *args):
calls.append((fd, cmd))
return original_fcntl(fd, cmd, *args)
monkeypatch.setattr(fcntl_mod, "fcntl", mock_fcntl)
with tempfile.NamedTemporaryFile() as tmp:
tmp.write(b"test data")
tmp.flush()
darwin.fdatasync(tmp.fileno())
assert any(cmd == fcntl_mod.F_FULLFSYNC for _, cmd in calls), "fdatasync should call fcntl with F_FULLFSYNC"
def test_fdatasync_falls_back_to_fsync(monkeypatch):
"""Verify os.fsync fallback when F_FULLFSYNC fails."""
import fcntl as fcntl_mod
from ...platform import darwin
fsync_calls = []
def mock_fcntl(fd, cmd, *args):
if cmd == fcntl_mod.F_FULLFSYNC:
raise OSError("F_FULLFSYNC not supported")
return 0
def mock_fsync(fd):
fsync_calls.append(fd)
monkeypatch.setattr(fcntl_mod, "fcntl", mock_fcntl)
monkeypatch.setattr(os, "fsync", mock_fsync)
with tempfile.NamedTemporaryFile() as tmp:
tmp.write(b"test data")
tmp.flush()
darwin.fdatasync(tmp.fileno())
assert len(fsync_calls) == 1, "Should fall back to os.fsync when F_FULLFSYNC fails"
def test_fdatasync_basic():
"""Integration: fdatasync completes on a real file without error."""
with tempfile.NamedTemporaryFile() as tmp:
tmp.write(b"test data for fdatasync")
tmp.flush()
fdatasync(tmp.fileno())
def test_sync_dir_basic():
"""Integration: sync_dir completes on a real directory without error."""
with tempfile.TemporaryDirectory() as tmpdir:
sync_dir(tmpdir)