Skip to content

Commit 5bb4298

Browse files
pks-tgitster
authored andcommitted
builtin/maintenance: split into foreground and background tasks
Both git-gc(1) and git-maintenance(1) have logic to daemonize so that the maintenance tasks are performed in the background. git-gc(1) has some special logic though to not perform _all_ housekeeping tasks in the background: both references and reflogs are still handled synchronously in the foreground. This split exists because otherwise it may easily happen that git-gc(1) keeps the "packed-refs" file locked for an extended amount of time, where the next Git command that wants to modify any reference could now fail. This was especially important in the past, where git-gc(1) was still executed directly as part of our automatic maintenance: git-gc(1) was invoked via `git gc --auto --detach`, so we knew to handle most of the maintenance tasks in the background while doing those parts that may cause locking issues in the foreground. We have since moved to git-maintenance(1), which is a more flexible replacement for git-gc(1). By default this command runs git-gc(1), only, but it can be configured to run different tasks, as well. This command does not know about the split between maintenance tasks that should run before and after detach though, and this has led to several bug reports about spurious locking errors for the "packed-refs" file. Prepare for a fix by introducing this split for maintenance tasks. Note that this commit does not yet change any of the tasks, so there should not (yet) be a change in behaviour. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3236e03 commit 5bb4298

File tree

1 file changed

+49
-21
lines changed

1 file changed

+49
-21
lines changed

builtin/gc.c

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,84 +1535,106 @@ static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts
15351535

15361536
typedef int (*maintenance_task_fn)(struct maintenance_run_opts *opts,
15371537
struct gc_config *cfg);
1538-
1539-
/*
1540-
* An auto condition function returns 1 if the task should run
1541-
* and 0 if the task should NOT run. See needs_to_gc() for an
1542-
* example.
1543-
*/
15441538
typedef int (*maintenance_auto_fn)(struct gc_config *cfg);
15451539

15461540
struct maintenance_task {
15471541
const char *name;
1548-
maintenance_task_fn fn;
1542+
1543+
/*
1544+
* Work that will be executed before detaching. This should not include
1545+
* tasks that may run for an extended amount of time as it does cause
1546+
* auto-maintenance to block until foreground tasks have been run.
1547+
*/
1548+
maintenance_task_fn foreground;
1549+
1550+
/*
1551+
* Work that will be executed after detaching. When not detaching the
1552+
* work will be run in the foreground, as well.
1553+
*/
1554+
maintenance_task_fn background;
1555+
1556+
/*
1557+
* An auto condition function returns 1 if the task should run and 0 if
1558+
* the task should NOT run. See needs_to_gc() for an example.
1559+
*/
15491560
maintenance_auto_fn auto_condition;
15501561
};
15511562

15521563
static const struct maintenance_task tasks[] = {
15531564
[TASK_PREFETCH] = {
15541565
.name = "prefetch",
1555-
.fn = maintenance_task_prefetch,
1566+
.background = maintenance_task_prefetch,
15561567
},
15571568
[TASK_LOOSE_OBJECTS] = {
15581569
.name = "loose-objects",
1559-
.fn = maintenance_task_loose_objects,
1570+
.background = maintenance_task_loose_objects,
15601571
.auto_condition = loose_object_auto_condition,
15611572
},
15621573
[TASK_INCREMENTAL_REPACK] = {
15631574
.name = "incremental-repack",
1564-
.fn = maintenance_task_incremental_repack,
1575+
.background = maintenance_task_incremental_repack,
15651576
.auto_condition = incremental_repack_auto_condition,
15661577
},
15671578
[TASK_GC] = {
15681579
.name = "gc",
1569-
.fn = maintenance_task_gc,
1580+
.background = maintenance_task_gc,
15701581
.auto_condition = need_to_gc,
15711582
},
15721583
[TASK_COMMIT_GRAPH] = {
15731584
.name = "commit-graph",
1574-
.fn = maintenance_task_commit_graph,
1585+
.background = maintenance_task_commit_graph,
15751586
.auto_condition = should_write_commit_graph,
15761587
},
15771588
[TASK_PACK_REFS] = {
15781589
.name = "pack-refs",
1579-
.fn = maintenance_task_pack_refs,
1590+
.background = maintenance_task_pack_refs,
15801591
.auto_condition = pack_refs_condition,
15811592
},
15821593
[TASK_REFLOG_EXPIRE] = {
15831594
.name = "reflog-expire",
1584-
.fn = maintenance_task_reflog_expire,
1595+
.background = maintenance_task_reflog_expire,
15851596
.auto_condition = reflog_expire_condition,
15861597
},
15871598
[TASK_WORKTREE_PRUNE] = {
15881599
.name = "worktree-prune",
1589-
.fn = maintenance_task_worktree_prune,
1600+
.background = maintenance_task_worktree_prune,
15901601
.auto_condition = worktree_prune_condition,
15911602
},
15921603
[TASK_RERERE_GC] = {
15931604
.name = "rerere-gc",
1594-
.fn = maintenance_task_rerere_gc,
1605+
.background = maintenance_task_rerere_gc,
15951606
.auto_condition = rerere_gc_condition,
15961607
},
15971608
};
15981609

1610+
enum task_phase {
1611+
TASK_PHASE_FOREGROUND,
1612+
TASK_PHASE_BACKGROUND,
1613+
};
1614+
15991615
static int maybe_run_task(const struct maintenance_task *task,
16001616
struct repository *repo,
16011617
struct maintenance_run_opts *opts,
1602-
struct gc_config *cfg)
1618+
struct gc_config *cfg,
1619+
enum task_phase phase)
16031620
{
1621+
int foreground = (phase == TASK_PHASE_FOREGROUND);
1622+
maintenance_task_fn fn = foreground ? task->foreground : task->background;
1623+
const char *region = foreground ? "maintenance foreground" : "maintenance";
16041624
int ret = 0;
16051625

1626+
if (!fn)
1627+
return 0;
16061628
if (opts->auto_flag &&
16071629
(!task->auto_condition || !task->auto_condition(cfg)))
16081630
return 0;
16091631

1610-
trace2_region_enter("maintenance", task->name, repo);
1611-
if (task->fn(opts, cfg)) {
1632+
trace2_region_enter(region, task->name, repo);
1633+
if (fn(opts, cfg)) {
16121634
error(_("task '%s' failed"), task->name);
16131635
ret = 1;
16141636
}
1615-
trace2_region_leave("maintenance", task->name, repo);
1637+
trace2_region_leave(region, task->name, repo);
16161638

16171639
return ret;
16181640
}
@@ -1641,6 +1663,11 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
16411663
}
16421664
free(lock_path);
16431665

1666+
for (size_t i = 0; i < opts->tasks_nr; i++)
1667+
if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
1668+
TASK_PHASE_FOREGROUND))
1669+
result = 1;
1670+
16441671
/* Failure to daemonize is ok, we'll continue in foreground. */
16451672
if (opts->detach > 0) {
16461673
trace2_region_enter("maintenance", "detach", the_repository);
@@ -1649,7 +1676,8 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
16491676
}
16501677

16511678
for (size_t i = 0; i < opts->tasks_nr; i++)
1652-
if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg))
1679+
if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
1680+
TASK_PHASE_BACKGROUND))
16531681
result = 1;
16541682

16551683
rollback_lock_file(&lk);

0 commit comments

Comments
 (0)