Skip to content

Commit 7590137

Browse files
vdyeGit for Windows Build Agent
authored andcommitted
Merge branch 'ns-batch-fsync' into HEAD
2 parents 6b2b8a9 + 981a256 commit 7590137

25 files changed

+497
-114
lines changed

Documentation/config/core.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,14 @@ core.fsyncMethod::
628628
* `writeout-only` issues pagecache writeback requests, but depending on the
629629
filesystem and storage hardware, data added to the repository may not be
630630
durable in the event of a system crash. This is the default mode on macOS.
631+
* `batch` enables a mode that uses writeout-only flushes to stage multiple
632+
updates in the disk writeback cache and then does a single full fsync of
633+
a dummy file to trigger the disk cache flush at the end of the operation.
634+
+
635+
Currently `batch` mode only applies to loose-object files. Other repository
636+
data is made durable as if `fsync` was specified. This mode is expected to
637+
be as safe as `fsync` on macOS for repos stored on HFS+ or APFS filesystems
638+
and on Windows for repos stored on NTFS or ReFS filesystems.
631639

632640
core.fsyncObjectFiles::
633641
This boolean will enable 'fsync()' when writing object files.

builtin/add.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,16 @@ int add_files_to_cache(const char *prefix,
141141
rev.diffopt.format_callback_data = &data;
142142
rev.diffopt.flags.override_submodule_config = 1;
143143
rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
144+
145+
/*
146+
* Use an ODB transaction to optimize adding multiple objects.
147+
* This function is invoked from commands other than 'add', which
148+
* may not have their own transaction active.
149+
*/
150+
begin_odb_transaction();
144151
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
152+
end_odb_transaction();
153+
145154
clear_pathspec(&rev.prune_data);
146155
return !!data.add_errors;
147156
}
@@ -670,7 +679,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
670679
string_list_clear(&only_match_skip_worktree, 0);
671680
}
672681

673-
plug_bulk_checkin();
682+
begin_odb_transaction();
674683

675684
if (add_renormalize)
676685
exit_status |= renormalize_tracked_files(&pathspec, flags);
@@ -682,7 +691,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
682691

683692
if (chmod_arg && pathspec.nr)
684693
exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only);
685-
unplug_bulk_checkin();
694+
end_odb_transaction();
686695

687696
finish:
688697
if (write_locked_index(&the_index, &lock_file,

builtin/unpack-objects.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "builtin.h"
22
#include "cache.h"
3+
#include "bulk-checkin.h"
34
#include "config.h"
45
#include "object-store.h"
56
#include "object.h"
@@ -503,10 +504,12 @@ static void unpack_all(void)
503504
if (!quiet)
504505
progress = start_progress(_("Unpacking objects"), nr_objects);
505506
CALLOC_ARRAY(obj_list, nr_objects);
507+
begin_odb_transaction();
506508
for (i = 0; i < nr_objects; i++) {
507509
unpack_one(i);
508510
display_progress(progress, i + 1);
509511
}
512+
end_odb_transaction();
510513
stop_progress(&progress);
511514

512515
if (delta_list)

builtin/update-index.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
#define USE_THE_INDEX_COMPATIBILITY_MACROS
77
#include "cache.h"
8+
#include "bulk-checkin.h"
89
#include "config.h"
910
#include "lockfile.h"
1011
#include "quote.h"
@@ -1116,6 +1117,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
11161117
*/
11171118
parse_options_start(&ctx, argc, argv, prefix,
11181119
options, PARSE_OPT_STOP_AT_NON_OPTION);
1120+
1121+
/*
1122+
* Allow the object layer to optimize adding multiple objects in
1123+
* a batch.
1124+
*/
1125+
begin_odb_transaction();
11191126
while (ctx.argc) {
11201127
if (parseopt_state != PARSE_OPT_DONE)
11211128
parseopt_state = parse_options_step(&ctx, options,
@@ -1167,6 +1174,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
11671174
the_index.version = preferred_index_format;
11681175
}
11691176

1177+
/*
1178+
* It is possible, though unlikely, that a caller could use the verbose
1179+
* output to synchronize with addition of objects to the object
1180+
* database. The current implementation of ODB transactions leaves
1181+
* objects invisible while a transaction is active, so end the
1182+
* transaction here if verbose output is enabled.
1183+
*/
1184+
1185+
if (verbose)
1186+
end_odb_transaction();
1187+
11701188
if (read_from_stdin) {
11711189
struct strbuf buf = STRBUF_INIT;
11721190
struct strbuf unquoted = STRBUF_INIT;
@@ -1190,6 +1208,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
11901208
strbuf_release(&buf);
11911209
}
11921210

1211+
/*
1212+
* By now we have added all of the new objects
1213+
*/
1214+
if (!verbose)
1215+
end_odb_transaction();
1216+
11931217
if (split_index > 0) {
11941218
if (git_config_get_split_index() == 0)
11951219
warning(_("core.splitIndex is set to false; "

bulk-checkin.c

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
*/
44
#include "cache.h"
55
#include "bulk-checkin.h"
6+
#include "lockfile.h"
67
#include "repository.h"
78
#include "csum-file.h"
89
#include "pack.h"
910
#include "strbuf.h"
11+
#include "string-list.h"
12+
#include "tmp-objdir.h"
1013
#include "packfile.h"
1114
#include "object-store.h"
1215

13-
static struct bulk_checkin_state {
14-
unsigned plugged:1;
16+
static int odb_transaction_nesting;
17+
18+
static struct tmp_objdir *bulk_fsync_objdir;
1519

20+
static struct bulk_checkin_state {
1621
char *pack_tmp_name;
1722
struct hashfile *f;
1823
off_t offset;
@@ -21,7 +26,7 @@ static struct bulk_checkin_state {
2126
struct pack_idx_entry **written;
2227
uint32_t alloc_written;
2328
uint32_t nr_written;
24-
} state;
29+
} bulk_checkin_state;
2530

2631
static void finish_tmp_packfile(struct strbuf *basename,
2732
const char *pack_tmp_name,
@@ -80,6 +85,40 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
8085
reprepare_packed_git(the_repository);
8186
}
8287

88+
/*
89+
* Cleanup after batch-mode fsync_object_files.
90+
*/
91+
static void do_batch_fsync(void)
92+
{
93+
struct strbuf temp_path = STRBUF_INIT;
94+
struct tempfile *temp;
95+
96+
if (!bulk_fsync_objdir)
97+
return;
98+
99+
/*
100+
* Issue a full hardware flush against a temporary file to ensure
101+
* that all objects are durable before any renames occur. The code in
102+
* fsync_loose_object_bulk_checkin has already issued a writeout
103+
* request, but it has not flushed any writeback cache in the storage
104+
* hardware or any filesystem logs. This fsync call acts as a barrier
105+
* to ensure that the data in each new object file is durable before
106+
* the final name is visible.
107+
*/
108+
strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", get_object_directory());
109+
temp = xmks_tempfile(temp_path.buf);
110+
fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
111+
delete_tempfile(&temp);
112+
strbuf_release(&temp_path);
113+
114+
/*
115+
* Make the object files visible in the primary ODB after their data is
116+
* fully durable.
117+
*/
118+
tmp_objdir_migrate(bulk_fsync_objdir);
119+
bulk_fsync_objdir = NULL;
120+
}
121+
83122
static int already_written(struct bulk_checkin_state *state, struct object_id *oid)
84123
{
85124
int i;
@@ -274,25 +313,63 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
274313
return 0;
275314
}
276315

316+
void prepare_loose_object_bulk_checkin(void)
317+
{
318+
/*
319+
* We lazily create the temporary object directory
320+
* the first time an object might be added, since
321+
* callers may not know whether any objects will be
322+
* added at the time they call begin_odb_transaction.
323+
*/
324+
if (!odb_transaction_nesting || bulk_fsync_objdir)
325+
return;
326+
327+
bulk_fsync_objdir = tmp_objdir_create("bulk-fsync");
328+
if (bulk_fsync_objdir)
329+
tmp_objdir_replace_primary_odb(bulk_fsync_objdir, 0);
330+
}
331+
332+
void fsync_loose_object_bulk_checkin(int fd, const char *filename)
333+
{
334+
/*
335+
* If we have an active ODB transaction, we issue a call that
336+
* cleans the filesystem page cache but avoids a hardware flush
337+
* command. Later on we will issue a single hardware flush
338+
* before as part of do_batch_fsync.
339+
*/
340+
if (!bulk_fsync_objdir ||
341+
git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
342+
fsync_or_die(fd, filename);
343+
}
344+
}
345+
277346
int index_bulk_checkin(struct object_id *oid,
278347
int fd, size_t size, enum object_type type,
279348
const char *path, unsigned flags)
280349
{
281-
int status = deflate_to_pack(&state, oid, fd, size, type,
350+
int status = deflate_to_pack(&bulk_checkin_state, oid, fd, size, type,
282351
path, flags);
283-
if (!state.plugged)
284-
finish_bulk_checkin(&state);
352+
if (!odb_transaction_nesting)
353+
finish_bulk_checkin(&bulk_checkin_state);
285354
return status;
286355
}
287356

288-
void plug_bulk_checkin(void)
357+
void begin_odb_transaction(void)
289358
{
290-
state.plugged = 1;
359+
odb_transaction_nesting += 1;
291360
}
292361

293-
void unplug_bulk_checkin(void)
362+
void end_odb_transaction(void)
294363
{
295-
state.plugged = 0;
296-
if (state.f)
297-
finish_bulk_checkin(&state);
364+
odb_transaction_nesting -= 1;
365+
if (odb_transaction_nesting < 0)
366+
BUG("Unbalanced ODB transaction nesting");
367+
368+
if (odb_transaction_nesting)
369+
return;
370+
371+
if (bulk_checkin_state.f)
372+
finish_bulk_checkin(&bulk_checkin_state);
373+
374+
do_batch_fsync();
298375
}

bulk-checkin.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,24 @@
66

77
#include "cache.h"
88

9+
void prepare_loose_object_bulk_checkin(void);
10+
void fsync_loose_object_bulk_checkin(int fd, const char *filename);
11+
912
int index_bulk_checkin(struct object_id *oid,
1013
int fd, size_t size, enum object_type type,
1114
const char *path, unsigned flags);
1215

13-
void plug_bulk_checkin(void);
14-
void unplug_bulk_checkin(void);
16+
/*
17+
* Tell the object database to optimize for adding
18+
* multiple objects. end_odb_transaction must be called
19+
* to make new objects visible.
20+
*/
21+
void begin_odb_transaction(void);
22+
23+
/*
24+
* Tell the object database to make any objects from the
25+
* current transaction visible.
26+
*/
27+
void end_odb_transaction(void);
1528

1629
#endif

cache-tree.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "tree.h"
44
#include "tree-walk.h"
55
#include "cache-tree.h"
6+
#include "bulk-checkin.h"
67
#include "object-store.h"
78
#include "replace-object.h"
89
#include "promisor-remote.h"
@@ -474,8 +475,10 @@ int cache_tree_update(struct index_state *istate, int flags)
474475

475476
trace_performance_enter();
476477
trace2_region_enter("cache_tree", "update", the_repository);
478+
begin_odb_transaction();
477479
i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
478480
"", 0, &skip, flags);
481+
end_odb_transaction();
479482
trace2_region_leave("cache_tree", "update", the_repository);
480483
trace_performance_leave("cache_tree_update");
481484
if (i < 0)

cache.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,10 @@ enum fsync_component {
10311031
FSYNC_COMPONENT_INDEX | \
10321032
FSYNC_COMPONENT_REFERENCE)
10331033

1034+
#ifndef FSYNC_COMPONENTS_PLATFORM_DEFAULT
1035+
#define FSYNC_COMPONENTS_PLATFORM_DEFAULT FSYNC_COMPONENTS_DEFAULT
1036+
#endif
1037+
10341038
/*
10351039
* A bitmask indicating which components of the repo should be fsynced.
10361040
*/
@@ -1040,7 +1044,8 @@ extern int use_fsync;
10401044

10411045
enum fsync_method {
10421046
FSYNC_METHOD_FSYNC,
1043-
FSYNC_METHOD_WRITEOUT_ONLY
1047+
FSYNC_METHOD_WRITEOUT_ONLY,
1048+
FSYNC_METHOD_BATCH,
10441049
};
10451050

10461051
extern enum fsync_method fsync_method;
@@ -1767,6 +1772,11 @@ void fsync_or_die(int fd, const char *);
17671772
int fsync_component(enum fsync_component component, int fd);
17681773
void fsync_component_or_die(enum fsync_component component, int fd, const char *msg);
17691774

1775+
static inline int batch_fsync_enabled(enum fsync_component component)
1776+
{
1777+
return (fsync_components & component) && (fsync_method == FSYNC_METHOD_BATCH);
1778+
}
1779+
17701780
ssize_t read_in_full(int fd, void *buf, size_t count);
17711781
ssize_t write_in_full(int fd, const void *buf, size_t count);
17721782
ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);

compat/mingw.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ int mingw_getpagesize(void);
332332
int win32_fsync_no_flush(int fd);
333333
#define fsync_no_flush win32_fsync_no_flush
334334

335+
#define FSYNC_COMPONENTS_PLATFORM_DEFAULT (FSYNC_COMPONENTS_DEFAULT | FSYNC_COMPONENT_LOOSE_OBJECT)
336+
#define FSYNC_METHOD_DEFAULT (FSYNC_METHOD_BATCH)
337+
335338
struct rlimit {
336339
unsigned int rlim_cur;
337340
};

config.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1342,7 +1342,7 @@ static const struct fsync_component_name {
13421342

13431343
static enum fsync_component parse_fsync_components(const char *var, const char *string)
13441344
{
1345-
enum fsync_component current = FSYNC_COMPONENTS_DEFAULT;
1345+
enum fsync_component current = FSYNC_COMPONENTS_PLATFORM_DEFAULT;
13461346
enum fsync_component positive = 0, negative = 0;
13471347

13481348
while (string) {
@@ -1688,6 +1688,8 @@ int git_default_core_config(const char *var, const char *value, void *cb)
16881688
fsync_method = FSYNC_METHOD_FSYNC;
16891689
else if (!strcmp(value, "writeout-only"))
16901690
fsync_method = FSYNC_METHOD_WRITEOUT_ONLY;
1691+
else if (!strcmp(value, "batch"))
1692+
fsync_method = FSYNC_METHOD_BATCH;
16911693
else
16921694
warning(_("ignoring unknown core.fsyncMethod value '%s'"), value);
16931695

git-compat-util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,11 +1281,13 @@ __attribute__((format (printf, 3, 4))) NORETURN
12811281
void BUG_fl(const char *file, int line, const char *fmt, ...);
12821282
#define BUG(...) BUG_fl(__FILE__, __LINE__, __VA_ARGS__)
12831283

1284+
#ifndef FSYNC_METHOD_DEFAULT
12841285
#ifdef __APPLE__
12851286
#define FSYNC_METHOD_DEFAULT FSYNC_METHOD_WRITEOUT_ONLY
12861287
#else
12871288
#define FSYNC_METHOD_DEFAULT FSYNC_METHOD_FSYNC
12881289
#endif
1290+
#endif
12891291

12901292
enum fsync_action {
12911293
FSYNC_WRITEOUT_ONLY,

0 commit comments

Comments
 (0)