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

Re: [PATCH] config: retry acquiring config.lock for 100ms

From
Patrick Steinhardt <ps@pks.im>
Date
Apr 8, 2026, 10:34 UTC
Message-ID
<adYvSZeN0ZVqwRhi@pks.im>
In-Reply-To
<20260403100135.3901610-1-joerg@thalheim.io>
On Fri, Apr 03, 2026 at 12:01:35PM +0200, Joerg Thalheim wrote:
Show 16 quoted lines
> From: Jörg Thalheim <joerg@thalheim.io>
> 
> When multiple processes write to a config file concurrently, they
> contend on its ".lock" file, which is acquired via open(O_EXCL) with
> no retry. The losers fail immediately with "could not lock config
> file". Two processes writing unrelated keys (say, "branch.a.remote"
> and "branch.b.remote") have no semantic conflict, yet one of them
> fails for a purely mechanical reason.
> 
> This bites in practice when running `git worktree add -b` concurrently
> against the same repository. Each invocation makes several writes to
> ".git/config" to set up branch tracking, and tooling that creates
> worktrees in parallel sees intermittent failures. Worse, `git worktree
> add` does not propagate the failed config write to its exit code: the
> worktree is created and the command exits 0, but tracking
> configuration is silently dropped.
This very much sounds like a bug that is worth fixing independently.
Show 6 quoted lines
> The lock is held only for the duration of rewriting a small file, so
> retrying for 100 ms papers over any realistic contention while still
> failing fast if a stale lock has been left behind by a crashed
> process. This mirrors what we already do for individual reference
> locks (4ff0f01cb7 (refs: retry acquiring reference locks for 100ms,
> 2017-08-21)).

Famous last words :) Experience tells me that any timeout value that isn't excessive will eventually be hit in some production system. Which raises the question whether we want to make the timeout configurable, similar to "core.filesRefLockTimeout" and "core.packedRefsTimeout".

Show 39 quoted lines
> diff --git a/config.c b/config.c
> index 156f2a24fa..f7aff8725d 100644
> --- a/config.c
> +++ b/config.c
> @@ -2903,6 +2903,14 @@ char *git_config_prepare_comment_string(const char *comment)
>  	return prepared;
>  }
>  
> +/*
> + * How long to retry acquiring config.lock when another process holds it.
> + * The lock is held only for the duration of rewriting a small file, so
> + * 100 ms covers any realistic contention while still failing fast if
> + * a stale lock has been left behind by a crashed process.
> + */
> +#define CONFIG_LOCK_TIMEOUT_MS 100
> +
>  static void validate_comment_string(const char *comment)
>  {
>  	size_t leading_blanks;
> @@ -2986,7 +2994,8 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
>  	 * The lock serves a purpose in addition to locking: the new
>  	 * contents of .git/config will be written into it.
>  	 */
> -	fd = hold_lock_file_for_update(&lock, config_filename, 0);
> +	fd = hold_lock_file_for_update_timeout(&lock, config_filename, 0,
> +					       CONFIG_LOCK_TIMEOUT_MS);
>  	if (fd < 0) {
>  		error_errno(_("could not lock config file %s"), config_filename);
>  		ret = CONFIG_NO_LOCK;
> @@ -3331,7 +3340,8 @@ static int repo_config_copy_or_rename_section_in_file(
>  	if (!config_filename)
>  		config_filename = filename_buf = repo_git_path(r, "config");
>  
> -	out_fd = hold_lock_file_for_update(&lock, config_filename, 0);
> +	out_fd = hold_lock_file_for_update_timeout(&lock, config_filename, 0,
> +						   CONFIG_LOCK_TIMEOUT_MS);
>  	if (out_fd < 0) {
>  		ret = error(_("could not lock config file %s"), config_filename);
>  		goto out;

Okay, so we now handle this more gracefully with concurrent writers. But even if we handle that more gracefully, we still don't really have any guarantee that the outcome will be reasonable, or even close.

The main difference to the locking timeouts we have for refs is that we have an easy way to check for conflicts there: we simpliy verify that the refs we want to update haven't moved from their expected value. We have no such thing for our configuration though, so two processes that race with one another will happily overwrite each other's changes.

Honestly though, I'm not really sure what to make with this. We could of course also add some validation that the configuration we want to set hasn't been modified meanwhile. But that would now lead to a situation where we have to update every single caller in our tree to make use of the new mechanism, which would be a bunch of work.

And adding the timeout doesn't really change the status quo, either. We already have the case that we'll happily overwrite changes made by concurrent processes. The only thing that changes is that we make it more likely for concurrent changes to succeed.

So I'm a bit cautious, but I think overall I'm fine with this?
Thanks!
Patrick
Previous: Junio C HamanoNext: Junio C Hamano
Message 3 of 18 in “config: retry acquiring config.lock for 100ms”
  1. config: retry acquiring config.lock for 100msJoerg Thalheim, Apr 3, 2026
  2. Junio C HamanoApr 3, 2026
  3. Patrick SteinhardtApr 8, 2026
  4. Junio C HamanoMay 11, 2026
  5. Patrick SteinhardtMay 11, 2026
  6. Jörg ThalheimMay 11, 2026
  7. Patrick SteinhardtMay 11, 2026
  8. Johannes SchindelinMay 17, 2026
  9. config: retry acquiring config.lock, configurable via core.configLockTimeoutJoerg Thalheim, May 17, 2026
  10. Junio C HamanoMay 18, 2026
  11. Patrick SteinhardtMay 18, 2026
  12. Johannes SchindelinJul 8, 2026
  13. Junio C HamanoJul 8, 2026
  14. Johannes SchindelinMay 28, 2026
  15. Junio C HamanoMay 28, 2026
  16. Johannes SchindelinJul 7, 2026
  17. Junio C HamanoJul 7, 2026
  18. Junio C HamanoJul 23, 2026

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.