git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 08/12] builtin/maintenance: let tasks do maintenance before and after detach

From
Patrick Steinhardt <ps@pks.im>
Date
May 30, 2025, 15:08 UTC
Message-ID
<20250530-b4-pks-maintenance-ref-lock-race-v2-8-d04e2f93e51f@pks.im>
In-Reply-To
<20250530-b4-pks-maintenance-ref-lock-race-v2-0-d04e2f93e51f@pks.im>

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 <ps@pks.im>
---
 builtin/gc.c | 48 +++++++++++++++++++++++++++++++++---------------
 1 file changed, 33 insertions(+), 15 deletions(-)
diff --git a/builtin/gc.c b/builtin/gc.c
index 447e5800846..f64bae0a825 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -1545,74 +1545,86 @@ typedef int (*maintenance_auto_fn)(struct gc_config *cfg);
 
 struct maintenance_task {
 	const char *name;
-	maintenance_task_fn fn;
+	maintenance_task_fn before_detach;
+	maintenance_task_fn after_detach;
 	maintenance_auto_fn auto_condition;
 };
 
 static const struct maintenance_task tasks[] = {
 	[TASK_PREFETCH] = {
 		.name = "prefetch",
-		.fn = maintenance_task_prefetch,
+		.after_detach = maintenance_task_prefetch,
 	},
 	[TASK_LOOSE_OBJECTS] = {
 		.name = "loose-objects",
-		.fn = maintenance_task_loose_objects,
+		.after_detach = maintenance_task_loose_objects,
 		.auto_condition = loose_object_auto_condition,
 	},
 	[TASK_INCREMENTAL_REPACK] = {
 		.name = "incremental-repack",
-		.fn = maintenance_task_incremental_repack,
+		.after_detach = maintenance_task_incremental_repack,
 		.auto_condition = incremental_repack_auto_condition,
 	},
 	[TASK_GC] = {
 		.name = "gc",
-		.fn = maintenance_task_gc,
+		.after_detach = maintenance_task_gc,
 		.auto_condition = need_to_gc,
 	},
 	[TASK_COMMIT_GRAPH] = {
 		.name = "commit-graph",
-		.fn = maintenance_task_commit_graph,
+		.after_detach = maintenance_task_commit_graph,
 		.auto_condition = should_write_commit_graph,
 	},
 	[TASK_PACK_REFS] = {
 		.name = "pack-refs",
-		.fn = maintenance_task_pack_refs,
+		.after_detach = maintenance_task_pack_refs,
 		.auto_condition = pack_refs_condition,
 	},
 	[TASK_REFLOG_EXPIRE] = {
 		.name = "reflog-expire",
-		.fn = maintenance_task_reflog_expire,
+		.after_detach = maintenance_task_reflog_expire,
 		.auto_condition = reflog_expire_condition,
 	},
 	[TASK_WORKTREE_PRUNE] = {
 		.name = "worktree-prune",
-		.fn = maintenance_task_worktree_prune,
+		.after_detach = maintenance_task_worktree_prune,
 		.auto_condition = worktree_prune_condition,
 	},
 	[TASK_RERERE_GC] = {
 		.name = "rerere-gc",
-		.fn = maintenance_task_rerere_gc,
+		.after_detach = maintenance_task_rerere_gc,
 		.auto_condition = rerere_gc_condition,
 	},
 };
 
+enum task_phase {
+	TASK_PHASE_BEFORE_DETACH,
+	TASK_PHASE_AFTER_DETACH,
+};
+
 static int maybe_run_task(const struct maintenance_task *task,
 			  struct repository *repo,
 			  struct maintenance_run_opts *opts,
-			  struct gc_config *cfg)
+			  struct gc_config *cfg,
+			  enum task_phase phase)
 {
+	int before = (phase == TASK_PHASE_BEFORE_DETACH);
+	maintenance_task_fn fn = before ? task->before_detach : task->after_detach;
+	const char *region = before ? "maintenance before" : "maintenance";
 	int ret = 0;
 
+	if (!fn)
+		return 0;
 	if (opts->auto_flag &&
 	    (!task->auto_condition || !task->auto_condition(cfg)))
 		return 0;
 
-	trace2_region_enter("maintenance", task->name, repo);
-	if (task->fn(opts, cfg)) {
+	trace2_region_enter(region, task->name, repo);
+	if (fn(opts, cfg)) {
 		error(_("task '%s' failed"), task->name);
 		ret = 1;
 	}
-	trace2_region_leave("maintenance", task->name, repo);
+	trace2_region_leave(region, task->name, repo);
 
 	return ret;
 }
@@ -1641,6 +1653,11 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
 	}
 	free(lock_path);
 
+	for (size_t i = 0; i < opts->tasks_nr; i++)
+		if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
+				   TASK_PHASE_BEFORE_DETACH))
+			result = 1;
+
 	/* Failure to daemonize is ok, we'll continue in foreground. */
 	if (opts->detach > 0) {
 		trace2_region_enter("maintenance", "detach", the_repository);
@@ -1649,7 +1666,8 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
 	}
 
 	for (size_t i = 0; i < opts->tasks_nr; i++)
-		if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg))
+		if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
+				   TASK_PHASE_AFTER_DETACH))
 			result = 1;
 
 	rollback_lock_file(&lk);
-- 
2.50.0.rc0.604.gd4ff7b7c86.dirty
Previous: Patrick SteinhardtNext: Patrick Steinhardt
Message 25 of 71 in “builtin/maintenance: fix ref lock races when detaching”
  1. 00/11 builtin/maintenance: fix ref lock races when detachingPatrick Steinhardt, May 27, 2025
  2. 01/11 builtin/gc: use designated field initializers for maintenance tasksPatrick Steinhardt, May 27, 2025
  3. 02/11 builtin/gc: drop redundant local variablePatrick Steinhardt, May 27, 2025
  4. 03/11 builtin/maintenance: centralize configuration of explicit tasksPatrick Steinhardt, May 27, 2025
  5. 04/11 builtin/maintenance: mark "--task=" and "--schedule=" as incompatiblePatrick Steinhardt, May 27, 2025
  6. Ramsay JonesMay 27, 2025
  7. Patrick SteinhardtMay 28, 2025
  8. 05/11 builtin/maintenance: stop modifying global array of tasksPatrick Steinhardt, May 27, 2025
  9. 06/11 builtin/maintenance: extract function to run tasksPatrick Steinhardt, May 27, 2025
  10. 07/11 builtin/maintenance: fix typedef for function pointersPatrick Steinhardt, May 27, 2025
  11. 08/11 builtin/maintenance: let tasks do maintenance before and after detachPatrick Steinhardt, May 27, 2025
  12. Ramsay JonesMay 27, 2025
  13. Patrick SteinhardtMay 28, 2025
  14. 09/11 builtin/maintenance: fix locking race when packing refs and reflogsPatrick Steinhardt, May 27, 2025
  15. 11/11 builtin/maintenance: fix locking race when handling "gc" taskPatrick Steinhardt, May 27, 2025
  16. 10/11 builtin/gc: avoid global state in `gc_before_repack()`Patrick Steinhardt, May 27, 2025
  17. 00/12 builtin/maintenance: fix ref lock races when detachingPatrick Steinhardt, May 30, 2025
  18. 01/12 builtin/gc: use designated field initializers for maintenance tasksPatrick Steinhardt, May 30, 2025
  19. 02/12 builtin/gc: drop redundant local variablePatrick Steinhardt, May 30, 2025
  20. 04/12 builtin/maintenance: mark "--task=" and "--schedule=" as incompatiblePatrick Steinhardt, May 30, 2025
  21. 03/12 builtin/maintenance: centralize configuration of explicit tasksPatrick Steinhardt, May 30, 2025
  22. 05/12 builtin/maintenance: stop modifying global array of tasksPatrick Steinhardt, May 30, 2025
  23. 07/12 builtin/maintenance: fix typedef for function pointersPatrick Steinhardt, May 30, 2025
  24. 06/12 builtin/maintenance: extract function to run tasksPatrick Steinhardt, May 30, 2025
  25. 08/12 builtin/maintenance: let tasks do maintenance before and after detachPatrick Steinhardt, May 30, 2025
  26. 09/12 builtin/maintenance: fix locking race when packing refs and reflogsPatrick Steinhardt, May 30, 2025
  27. 10/12 usage: allow dying without writing an error messagePatrick Steinhardt, May 30, 2025
  28. 11/12 builtin/gc: avoid global state in `gc_before_repack()`Patrick Steinhardt, May 30, 2025
  29. 12/12 builtin/maintenance: fix locking race when handling "gc" taskPatrick Steinhardt, May 30, 2025
  30. 00/12 builtin/maintenance: fix ref lock races when detachingPatrick Steinhardt, Jun 2, 2025
  31. 01/12 builtin/gc: use designated field initializers for maintenance tasksPatrick Steinhardt, Jun 2, 2025
  32. Karthik NayakJun 2, 2025
  33. 02/12 builtin/gc: drop redundant local variablePatrick Steinhardt, Jun 2, 2025
  34. 03/12 builtin/maintenance: centralize configuration of explicit tasksPatrick Steinhardt, Jun 2, 2025
  35. 04/12 builtin/maintenance: mark "--task=" and "--schedule=" as incompatiblePatrick Steinhardt, Jun 2, 2025
  36. 05/12 builtin/maintenance: stop modifying global array of tasksPatrick Steinhardt, Jun 2, 2025
  37. 06/12 builtin/maintenance: extract function to run tasksPatrick Steinhardt, Jun 2, 2025
  38. 07/12 builtin/maintenance: fix typedef for function pointersPatrick Steinhardt, Jun 2, 2025
  39. 08/12 builtin/maintenance: split into foreground and background tasksPatrick Steinhardt, Jun 2, 2025
  40. 09/12 builtin/maintenance: fix locking race when packing refs and reflogsPatrick Steinhardt, Jun 2, 2025
  41. Kristoffer HaugsbakkJun 2, 2025
  42. Patrick SteinhardtJun 3, 2025
  43. Kristoffer HaugsbakkJun 3, 2025
  44. 10/12 usage: allow dying without writing an error messagePatrick Steinhardt, Jun 2, 2025
  45. Karthik NayakJun 3, 2025
  46. Kristoffer HaugsbakkJun 3, 2025
  47. Karthik NayakJun 3, 2025
  48. Patrick SteinhardtJun 3, 2025
  49. Kristoffer HaugsbakkJun 3, 2025
  50. Junio C HamanoJun 3, 2025
  51. Patrick SteinhardtJun 5, 2025
  52. 11/12 builtin/gc: avoid global state in `gc_before_repack()`Patrick Steinhardt, Jun 2, 2025
  53. 12/12 builtin/maintenance: fix locking race when handling "gc" taskPatrick Steinhardt, Jun 2, 2025
  54. 00/12 builtin/maintenance: fix ref lock races when detachingPatrick Steinhardt, Jun 3, 2025
  55. 01/12 builtin/gc: use designated field initializers for maintenance tasksPatrick Steinhardt, Jun 3, 2025
  56. 02/12 builtin/gc: drop redundant local variablePatrick Steinhardt, Jun 3, 2025
  57. 03/12 builtin/maintenance: centralize configuration of explicit tasksPatrick Steinhardt, Jun 3, 2025
  58. 05/12 builtin/maintenance: stop modifying global array of tasksPatrick Steinhardt, Jun 3, 2025
  59. 04/12 builtin/maintenance: mark "--task=" and "--schedule=" as incompatiblePatrick Steinhardt, Jun 3, 2025
  60. Derrick StoleeJun 5, 2025
  61. 06/12 builtin/maintenance: extract function to run tasksPatrick Steinhardt, Jun 3, 2025
  62. 07/12 builtin/maintenance: fix typedef for function pointersPatrick Steinhardt, Jun 3, 2025
  63. 08/12 builtin/maintenance: split into foreground and background tasksPatrick Steinhardt, Jun 3, 2025
  64. 09/12 builtin/maintenance: fix locking race with refs and reflogs tasksPatrick Steinhardt, Jun 3, 2025
  65. 11/12 builtin/gc: avoid global state in `gc_before_repack()`Patrick Steinhardt, Jun 3, 2025
  66. 10/12 usage: allow dying without writing an error messagePatrick Steinhardt, Jun 3, 2025
  67. 12/12 builtin/maintenance: fix locking race when handling "gc" taskPatrick Steinhardt, Jun 3, 2025
  68. Kristoffer HaugsbakkJun 3, 2025
  69. Ben KnobleJun 4, 2025
  70. Derrick StoleeJun 5, 2025
  71. Patrick SteinhardtJun 6, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.