builtin/maintenance: fix locking race when handling "gc" task
The "gc" task has a similar locking race as the one that we have fixed for the "pack-refs" and "reflog-expire" tasks in preceding commits. Fix this by splitting up the logic of the "gc" task: - We execute `gc_before_repack()` in the foreground, which contains the logic that git-gc(1) itself would execute in the foreground, as well. - We spawn git-gc(1) after detaching, but with a new hidden flag that suppresses calling `gc_before_repack()`. Like this we have roughly the same logic as git-gc(1) itself and know to repack refs and reflogs before detaching, thus fixing the race. Note that `gc_before_repack()` is renamed to `gc_foreground_tasks()` to better reflect what this function does. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
parent
d2b084c660
commit
1b5074e614
41
builtin/gc.c
41
builtin/gc.c
|
@ -816,8 +816,8 @@ done:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_before_repack(struct maintenance_run_opts *opts,
|
static int gc_foreground_tasks(struct maintenance_run_opts *opts,
|
||||||
struct gc_config *cfg)
|
struct gc_config *cfg)
|
||||||
{
|
{
|
||||||
if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg))
|
if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg))
|
||||||
return error(FAILED_RUN, "pack-refs");
|
return error(FAILED_RUN, "pack-refs");
|
||||||
|
@ -837,6 +837,7 @@ int cmd_gc(int argc,
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
int daemonized = 0;
|
int daemonized = 0;
|
||||||
int keep_largest_pack = -1;
|
int keep_largest_pack = -1;
|
||||||
|
int skip_foreground_tasks = 0;
|
||||||
timestamp_t dummy;
|
timestamp_t dummy;
|
||||||
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
|
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
|
||||||
struct gc_config cfg = GC_CONFIG_INIT;
|
struct gc_config cfg = GC_CONFIG_INIT;
|
||||||
|
@ -869,6 +870,8 @@ int cmd_gc(int argc,
|
||||||
N_("repack all other packs except the largest pack")),
|
N_("repack all other packs except the largest pack")),
|
||||||
OPT_STRING(0, "expire-to", &cfg.repack_expire_to, N_("dir"),
|
OPT_STRING(0, "expire-to", &cfg.repack_expire_to, N_("dir"),
|
||||||
N_("pack prefix to store a pack containing pruned objects")),
|
N_("pack prefix to store a pack containing pruned objects")),
|
||||||
|
OPT_HIDDEN_BOOL(0, "skip-foreground-tasks", &skip_foreground_tasks,
|
||||||
|
N_("skip maintenance tasks typically done in the foreground")),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -952,14 +955,16 @@ int cmd_gc(int argc,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lock_repo_for_gc(force, &pid)) {
|
if (!skip_foreground_tasks) {
|
||||||
ret = 0;
|
if (lock_repo_for_gc(force, &pid)) {
|
||||||
goto out;
|
ret = 0;
|
||||||
}
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
if (gc_before_repack(&opts, &cfg) < 0)
|
if (gc_foreground_tasks(&opts, &cfg) < 0)
|
||||||
die(NULL);
|
die(NULL);
|
||||||
delete_tempfile(&pidfile);
|
delete_tempfile(&pidfile);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* failure to daemonize is ok, we'll continue
|
* failure to daemonize is ok, we'll continue
|
||||||
|
@ -988,8 +993,8 @@ int cmd_gc(int argc,
|
||||||
free(path);
|
free(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.detach <= 0)
|
if (opts.detach <= 0 && !skip_foreground_tasks)
|
||||||
gc_before_repack(&opts, &cfg);
|
gc_foreground_tasks(&opts, &cfg);
|
||||||
|
|
||||||
if (!repository_format_precious_objects) {
|
if (!repository_format_precious_objects) {
|
||||||
struct child_process repack_cmd = CHILD_PROCESS_INIT;
|
struct child_process repack_cmd = CHILD_PROCESS_INIT;
|
||||||
|
@ -1225,8 +1230,14 @@ static int maintenance_task_prefetch(struct maintenance_run_opts *opts,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int maintenance_task_gc(struct maintenance_run_opts *opts,
|
static int maintenance_task_gc_foreground(struct maintenance_run_opts *opts,
|
||||||
struct gc_config *cfg UNUSED)
|
struct gc_config *cfg)
|
||||||
|
{
|
||||||
|
return gc_foreground_tasks(opts, cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int maintenance_task_gc_background(struct maintenance_run_opts *opts,
|
||||||
|
struct gc_config *cfg UNUSED)
|
||||||
{
|
{
|
||||||
struct child_process child = CHILD_PROCESS_INIT;
|
struct child_process child = CHILD_PROCESS_INIT;
|
||||||
|
|
||||||
|
@ -1240,6 +1251,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts,
|
||||||
else
|
else
|
||||||
strvec_push(&child.args, "--no-quiet");
|
strvec_push(&child.args, "--no-quiet");
|
||||||
strvec_push(&child.args, "--no-detach");
|
strvec_push(&child.args, "--no-detach");
|
||||||
|
strvec_push(&child.args, "--skip-foreground-tasks");
|
||||||
|
|
||||||
return run_command(&child);
|
return run_command(&child);
|
||||||
}
|
}
|
||||||
|
@ -1571,7 +1583,8 @@ static const struct maintenance_task tasks[] = {
|
||||||
},
|
},
|
||||||
[TASK_GC] = {
|
[TASK_GC] = {
|
||||||
.name = "gc",
|
.name = "gc",
|
||||||
.background = maintenance_task_gc,
|
.foreground = maintenance_task_gc_foreground,
|
||||||
|
.background = maintenance_task_gc_background,
|
||||||
.auto_condition = need_to_gc,
|
.auto_condition = need_to_gc,
|
||||||
},
|
},
|
||||||
[TASK_COMMIT_GRAPH] = {
|
[TASK_COMMIT_GRAPH] = {
|
||||||
|
|
|
@ -49,9 +49,9 @@ test_expect_success 'run [--auto|--quiet]' '
|
||||||
git maintenance run --auto 2>/dev/null &&
|
git maintenance run --auto 2>/dev/null &&
|
||||||
GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
|
GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
|
||||||
git maintenance run --no-quiet 2>/dev/null &&
|
git maintenance run --no-quiet 2>/dev/null &&
|
||||||
test_subcommand git gc --quiet --no-detach <run-no-auto.txt &&
|
test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-no-auto.txt &&
|
||||||
test_subcommand ! git gc --auto --quiet --no-detach <run-auto.txt &&
|
test_subcommand ! git gc --auto --quiet --no-detach --skip-foreground-tasks <run-auto.txt &&
|
||||||
test_subcommand git gc --no-quiet --no-detach <run-no-quiet.txt
|
test_subcommand git gc --no-quiet --no-detach --skip-foreground-tasks <run-no-quiet.txt
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'maintenance.auto config option' '
|
test_expect_success 'maintenance.auto config option' '
|
||||||
|
@ -154,9 +154,9 @@ test_expect_success 'run --task=<task>' '
|
||||||
git maintenance run --task=commit-graph 2>/dev/null &&
|
git maintenance run --task=commit-graph 2>/dev/null &&
|
||||||
GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
|
GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
|
||||||
git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
|
git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
|
||||||
test_subcommand ! git gc --quiet --no-detach <run-commit-graph.txt &&
|
test_subcommand ! git gc --quiet --no-detach --skip-foreground-tasks <run-commit-graph.txt &&
|
||||||
test_subcommand git gc --quiet --no-detach <run-gc.txt &&
|
test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-gc.txt &&
|
||||||
test_subcommand git gc --quiet --no-detach <run-both.txt &&
|
test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-both.txt &&
|
||||||
test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
|
test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
|
||||||
test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
|
test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
|
||||||
test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
|
test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
|
||||||
|
|
Loading…
Reference in New Issue