On Tue, Dec 07, 2021 at 02:46:49AM +0000, Neeraj Singh via GitGitGadget wrote: > From: Neeraj Singh [snip] > --- a/compat/mingw.h > +++ b/compat/mingw.h > @@ -329,6 +329,9 @@ int mingw_getpagesize(void); > #define getpagesize mingw_getpagesize > #endif > > +int win32_fsync_no_flush(int fd); > +#define fsync_no_flush win32_fsync_no_flush > + > struct rlimit { > unsigned int rlim_cur; > }; > diff --git a/compat/win32/flush.c b/compat/win32/flush.c > new file mode 100644 > index 00000000000..75324c24ee7 > --- /dev/null > +++ b/compat/win32/flush.c > @@ -0,0 +1,28 @@ > +#include "../../git-compat-util.h" > +#include > +#include "lazyload.h" > + > +int win32_fsync_no_flush(int fd) > +{ > + IO_STATUS_BLOCK io_status; > + > +#define FLUSH_FLAGS_FILE_DATA_ONLY 1 > + > + DECLARE_PROC_ADDR(ntdll.dll, NTSTATUS, NtFlushBuffersFileEx, > + HANDLE FileHandle, ULONG Flags, PVOID Parameters, ULONG ParameterSize, > + PIO_STATUS_BLOCK IoStatusBlock); > + > + if (!INIT_PROC_ADDR(NtFlushBuffersFileEx)) { > + errno = ENOSYS; > + return -1; > + } I'm wondering whether it would make sense to fall back to fsync(3P) in case we cannot use writeout-only, but I see that were doing essentially that in `fsync_or_die()`. There is no indicator to the user though that writeout-only doesn't work -- do we want to print a one-time warning? > + memset(&io_status, 0, sizeof(io_status)); > + if (NtFlushBuffersFileEx((HANDLE)_get_osfhandle(fd), FLUSH_FLAGS_FILE_DATA_ONLY, > + NULL, 0, &io_status)) { > + errno = EINVAL; > + return -1; > + } > + > + return 0; > +} [snip] > diff --git a/wrapper.c b/wrapper.c > index 36e12119d76..1c5f2c87791 100644 > --- a/wrapper.c > +++ b/wrapper.c > @@ -546,6 +546,62 @@ int xmkstemp_mode(char *filename_template, int mode) > return fd; > } > > +int git_fsync(int fd, enum fsync_action action) > +{ > + switch (action) { > + case FSYNC_WRITEOUT_ONLY: > + > +#ifdef __APPLE__ > + /* > + * on macOS, fsync just causes filesystem cache writeback but does not > + * flush hardware caches. > + */ > + return fsync(fd); Below we're looping around `EINTR` -- are Apple systems never returning it? Patrick > +#endif > + > +#ifdef HAVE_SYNC_FILE_RANGE > + /* > + * On linux 2.6.17 and above, sync_file_range is the way to issue > + * a writeback without a hardware flush. An offset of 0 and size of 0 > + * indicates writeout of the entire file and the wait flags ensure that all > + * dirty data is written to the disk (potentially in a disk-side cache) > + * before we continue. > + */ > + > + return sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WAIT_BEFORE | > + SYNC_FILE_RANGE_WRITE | > + SYNC_FILE_RANGE_WAIT_AFTER); > +#endif > + > +#ifdef fsync_no_flush > + return fsync_no_flush(fd); > +#endif > + > + errno = ENOSYS; > + return -1; > + > + case FSYNC_HARDWARE_FLUSH: > + /* > + * On some platforms fsync may return EINTR. Try again in this > + * case, since callers asking for a hardware flush may die if > + * this function returns an error. > + */ > + for (;;) { > + int err; > +#ifdef __APPLE__ > + err = fcntl(fd, F_FULLFSYNC); > +#else > + err = fsync(fd); > +#endif > + if (err >= 0 || errno != EINTR) > + return err; > + } > + > + default: > + BUG("unexpected git_fsync(%d) call", action); > + } > +} > + > static int warn_if_unremovable(const char *op, const char *file, int rc) > { > int err; > diff --git a/write-or-die.c b/write-or-die.c > index 0b1ec8190b6..0702acdd5e8 100644 > --- a/write-or-die.c > +++ b/write-or-die.c > @@ -57,10 +57,12 @@ void fprintf_or_die(FILE *f, const char *fmt, ...) > > void fsync_or_die(int fd, const char *msg) > { > - while (fsync(fd) < 0) { > - if (errno != EINTR) > - die_errno("fsync error on '%s'", msg); > - } > + if (fsync_method == FSYNC_METHOD_WRITEOUT_ONLY && > + git_fsync(fd, FSYNC_WRITEOUT_ONLY) >= 0) > + return; > + > + if (git_fsync(fd, FSYNC_HARDWARE_FLUSH) < 0) > + die_errno("fsync error on '%s'", msg); > } > > void write_or_die(int fd, const void *buf, size_t count) > -- > gitgitgadget >