@@ -596,6 +596,14 @@ core.fsyncMethod::
* `writeout-only` issues pagecache writeback requests, but depending on the
filesystem and storage hardware, data added to the repository may not be
durable in the event of a system crash. This is the default mode on macOS.
+* `batch` enables a mode that uses writeout-only flushes to stage multiple
+ updates in the disk writeback cache and then does a single full fsync of
+ a dummy file to trigger the disk cache flush at the end of the operation.
++
+ Currently `batch` mode only applies to loose-object files. Other repository
+ data is made durable as if `fsync` was specified. This mode is expected to
+ be as safe as `fsync` on macOS for repos stored on HFS+ or APFS filesystems
+ and on Windows for repos stored on NTFS or ReFS filesystems.
core.fsyncObjectFiles::
This boolean will enable 'fsync()' when writing object files.
@@ -3,15 +3,20 @@
*/
#include "cache.h"
#include "bulk-checkin.h"
+#include "lockfile.h"
#include "repository.h"
#include "csum-file.h"
#include "pack.h"
#include "strbuf.h"
+#include "string-list.h"
+#include "tmp-objdir.h"
#include "packfile.h"
#include "object-store.h"
static int odb_transaction_nesting;
+static struct tmp_objdir *bulk_fsync_objdir;
+
static struct bulk_checkin_state {
char *pack_tmp_name;
struct hashfile *f;
@@ -80,6 +85,40 @@ clear_exit:
reprepare_packed_git(the_repository);
}
+/*
+ * Cleanup after batch-mode fsync_object_files.
+ */
+static void do_batch_fsync(void)
+{
+ struct strbuf temp_path = STRBUF_INIT;
+ struct tempfile *temp;
+
+ if (!bulk_fsync_objdir)
+ return;
+
+ /*
+ * Issue a full hardware flush against a temporary file to ensure
+ * that all objects are durable before any renames occur. The code in
+ * fsync_loose_object_bulk_checkin has already issued a writeout
+ * request, but it has not flushed any writeback cache in the storage
+ * hardware or any filesystem logs. This fsync call acts as a barrier
+ * to ensure that the data in each new object file is durable before
+ * the final name is visible.
+ */
+ strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", get_object_directory());
+ temp = xmks_tempfile(temp_path.buf);
+ fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
+ delete_tempfile(&temp);
+ strbuf_release(&temp_path);
+
+ /*
+ * Make the object files visible in the primary ODB after their data is
+ * fully durable.
+ */
+ tmp_objdir_migrate(bulk_fsync_objdir);
+ bulk_fsync_objdir = NULL;
+}
+
static int already_written(struct bulk_checkin_state *state, struct object_id *oid)
{
int i;
@@ -274,6 +313,36 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
return 0;
}
+void prepare_loose_object_bulk_checkin(void)
+{
+ /*
+ * We lazily create the temporary object directory
+ * the first time an object might be added, since
+ * callers may not know whether any objects will be
+ * added at the time they call begin_odb_transaction.
+ */
+ if (!odb_transaction_nesting || bulk_fsync_objdir)
+ return;
+
+ bulk_fsync_objdir = tmp_objdir_create("bulk-fsync");
+ if (bulk_fsync_objdir)
+ tmp_objdir_replace_primary_odb(bulk_fsync_objdir, 0);
+}
+
+void fsync_loose_object_bulk_checkin(int fd, const char *filename)
+{
+ /*
+ * If we have an active ODB transaction, we issue a call that
+ * cleans the filesystem page cache but avoids a hardware flush
+ * command. Later on we will issue a single hardware flush
+ * before as part of do_batch_fsync.
+ */
+ if (!bulk_fsync_objdir ||
+ git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
+ fsync_or_die(fd, filename);
+ }
+}
+
int index_bulk_checkin(struct object_id *oid,
int fd, size_t size, enum object_type type,
const char *path, unsigned flags)
@@ -301,4 +370,6 @@ void end_odb_transaction(void)
if (bulk_checkin_state.f)
finish_bulk_checkin(&bulk_checkin_state);
+
+ do_batch_fsync();
}
@@ -6,6 +6,9 @@
#include "cache.h"
+void prepare_loose_object_bulk_checkin(void);
+void fsync_loose_object_bulk_checkin(int fd, const char *filename);
+
int index_bulk_checkin(struct object_id *oid,
int fd, size_t size, enum object_type type,
const char *path, unsigned flags);
@@ -1040,7 +1040,8 @@ extern int use_fsync;
enum fsync_method {
FSYNC_METHOD_FSYNC,
- FSYNC_METHOD_WRITEOUT_ONLY
+ FSYNC_METHOD_WRITEOUT_ONLY,
+ FSYNC_METHOD_BATCH,
};
extern enum fsync_method fsync_method;
@@ -1767,6 +1768,11 @@ void fsync_or_die(int fd, const char *);
int fsync_component(enum fsync_component component, int fd);
void fsync_component_or_die(enum fsync_component component, int fd, const char *msg);
+static inline int batch_fsync_enabled(enum fsync_component component)
+{
+ return (fsync_components & component) && (fsync_method == FSYNC_METHOD_BATCH);
+}
+
ssize_t read_in_full(int fd, void *buf, size_t count);
ssize_t write_in_full(int fd, const void *buf, size_t count);
ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
@@ -1688,6 +1688,8 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
fsync_method = FSYNC_METHOD_FSYNC;
else if (!strcmp(value, "writeout-only"))
fsync_method = FSYNC_METHOD_WRITEOUT_ONLY;
+ else if (!strcmp(value, "batch"))
+ fsync_method = FSYNC_METHOD_BATCH;
else
warning(_("ignoring unknown core.fsyncMethod value '%s'"), value);
@@ -1893,7 +1893,9 @@ static void close_loose_object(int fd, const char *filename)
if (the_repository->objects->odb->will_destroy)
goto out;
- if (fsync_object_files > 0)
+ if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
+ fsync_loose_object_bulk_checkin(fd, filename);
+ else if (fsync_object_files > 0)
fsync_or_die(fd, filename);
else
fsync_component_or_die(FSYNC_COMPONENT_LOOSE_OBJECT, fd,
@@ -1961,6 +1963,9 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
static struct strbuf tmp_file = STRBUF_INIT;
static struct strbuf filename = STRBUF_INIT;
+ if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
+ prepare_loose_object_bulk_checkin();
+
loose_object_path(the_repository, &filename, oid);
fd = create_tmpfile(&tmp_file, filename.buf);