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

Re: [PATCH] lockfile: add PID file for debugging stale locks

From
Taylor Blau <me@ttaylorr.com>
Date
Dec 3, 2025, 23:39 UTC
Message-ID
<aTDKK6Ibcx35q3Tz@nand.local>
In-Reply-To
<pull.2011.git.1764688047077.gitgitgadget@gmail.com>
Hi Paulo,
On Tue, Dec 02, 2025 at 03:07:27PM +0000, Paulo Casaretto via GitGitGadget wrote:
> The .lock.pid file is created when a lock is acquired (if enabled), and
> automatically cleaned up when the lock is released (via commit or
> rollback). The file is registered as a tempfile so it gets cleaned up
> by signal and atexit handlers if the process terminates abnormally.

(For the purposes of this review, I'll ignore the naming conventions that are discussed elsewhere in the thread, which I think can be resolved separately of any technical concerns.)

Show 17 quoted lines
> diff --git a/Documentation/git.adoc b/Documentation/git.adoc
> index ce099e78b8..6fdd509d34 100644
> --- a/Documentation/git.adoc
> +++ b/Documentation/git.adoc
> @@ -1010,6 +1010,16 @@ be in the future).
>  	the background which do not want to cause lock contention with
>  	other operations on the repository.  Defaults to `1`.
>
> +`GIT_LOCK_PID_INFO`::
> +	If this Boolean environment variable is set to `1`, Git will create
> +	a `.lock.pid` file alongside each lock file containing the PID of the
> +	process that created the lock. This information is displayed in error
> +	messages when a lock conflict occurs, making it easier to identify
> +	stale locks or debug locking issues. The PID files are automatically
> +	cleaned up via signal and atexit handlers; however, if a process is
> +	terminated abnormally (e.g., SIGKILL), the file may remain as a stale
> +	indicator. Disabled by default.

Regardless of whether or not we expose this functionality behind an environment variable or configuration, I think it would be nice to be able to turn PID tracking on and off for different components (e.g., for scenarios where you care about who is holding open, say, $GIT_DIR/index, but not who is creating a lock during ref creation).

If we determined this through an environment variable, I think it would be reasonable to adopt the convention from the "core.fsync" configuration and use a comma-separated list. Alternatively, we could adopt that same convention for a configuration variable, say, "core.lockfile".

But I think that having this exposed as a per-component setting via configuration is preferable than a global switch, since callers don't have to remember to set this variable in their environment to get the desired effect. Callers that want to opt-in or out of this feature on a one-off basis can still override the configuration via the top-level "-c" flag.

Show 29 quoted lines
>  `GIT_REDIRECT_STDIN`::
>  `GIT_REDIRECT_STDOUT`::
>  `GIT_REDIRECT_STDERR`::
> diff --git a/lockfile.c b/lockfile.c
> index 1d5ed01682..4a694b9c3d 100644
> --- a/lockfile.c
> +++ b/lockfile.c
> @@ -6,6 +6,9 @@
>  #include "abspath.h"
>  #include "gettext.h"
>  #include "lockfile.h"
> +#include "parse.h"
> +#include "strbuf.h"
> +#include "wrapper.h"
>
>  /*
>   * path = absolute or relative path name
> @@ -71,6 +74,62 @@ static void resolve_symlink(struct strbuf *path)
>  	strbuf_reset(&link);
>  }
>
> +/*
> + * Lock PID file functions - write PID to a .lock.pid file alongside
> + * the lock file for debugging stale locks. The PID file is registered
> + * as a tempfile so it gets cleaned up by signal/atexit handlers.
> + */
> +
> +static int lock_pid_info_enabled(void)
> +{

With the above suggestion, I think this function would get a little more complicated to instead take a component argument. That's not a huge deal in and of itself, but callers that *create* a lockfile will have to somehow pass in the component name when acquiring the lock.

Show 15 quoted lines
> +	return git_env_bool(GIT_LOCK_PID_INFO_ENVIRONMENT, 0);
> +}
> +
> +static struct tempfile *create_lock_pid_file(const char *lock_path, int mode)
> +{
> +	struct strbuf pid_path = STRBUF_INIT;
> +	struct strbuf content = STRBUF_INIT;
> +	struct tempfile *pid_tempfile = NULL;
> +	int fd;
> +
> +	if (!lock_pid_info_enabled())
> +		return NULL;
> +
> +	strbuf_addf(&pid_path, "%s%s", lock_path, LOCK_PID_SUFFIX);
> +	fd = open(pid_path.buf, O_WRONLY | O_CREAT | O_TRUNC, mode);

I'm not sure whether or not we should pass O_EXCL here, but I think it depends on the naming convention we pick.

> +	if (fd >= 0) {

This is a style preference, but I'd suggest handling the happy path outside of this conditional if possible by inverting it. Perhaps something like:

    if (fd < 0)
        goto out;
    strbuf_addf(&content, ...);
    if (write_in_full(fd, content.buf, content.len) < 0)
        warning_errno(...);
    close(fd);
    pid_tempfile = register_tempfile(pid_path.buf);
    out:
        strbuf_release(...);
        return pid_tempfile;
Show 12 quoted lines
> +static int read_lock_pid(const char *lock_path, uintmax_t *pid_out)
> +{
> +	struct strbuf pid_path = STRBUF_INIT;
> +	struct strbuf content = STRBUF_INIT;
> +	int ret = -1;
> +
> +	strbuf_addf(&pid_path, "%s%s", lock_path, LOCK_PID_SUFFIX);
> +	if (strbuf_read_file(&content, pid_path.buf, LOCK_PID_MAXLEN) > 0) {
> +		char *endptr;
> +		*pid_out = strtoumax(content.buf, &endptr, 10);
> +		if (*pid_out > 0 && (*endptr == '\n' || *endptr == '\0'))
> +			ret = 0;

Same note here. I'd suggest setting "ret = 0" during initialization, and inverting this conditional to:

    if (*pid_out <= 0 || (*endptr != '\n' && *endptr != '\0')) {
        warning(...);
        ret = -1;
        goto out;
    }
Show 18 quoted lines
> +		else
> +			warning(_("malformed lock pid file '%s'"), pid_path.buf);
> +	}
> +	strbuf_release(&pid_path);
> +	strbuf_release(&content);
> +	return ret;
> +}
> +
>  /* Make sure errno contains a meaningful value on error */
>  static int lock_file(struct lock_file *lk, const char *path, int flags,
>  		     int mode)
> @@ -80,9 +139,12 @@ static int lock_file(struct lock_file *lk, const char *path, int flags,
>  	strbuf_addstr(&filename, path);
>  	if (!(flags & LOCK_NO_DEREF))
>  		resolve_symlink(&filename);
> -
>  	strbuf_addstr(&filename, LOCK_SUFFIX);
> +

These look like stray whitespace changes that were left behind from development.

Show 11 quoted lines
> +		if (pid_status == 1)
> +			strbuf_addf(buf, _("Lock is held by process %"PRIuMAX". "
> +			    "Wait for it to finish, or remove the lock file to continue"),
> +			    pid);
> +		else if (pid_status == -1)
> +			strbuf_addf(buf, _("Lock was held by process %"PRIuMAX", "
> +			    "which is no longer running. Remove the stale lock file to continue"),
> +			    pid);
> +		else
> +			strbuf_addstr(buf, _("Another git process seems to be running in this repository. "
> +			    "Wait for it to finish, or remove the lock file to continue"));

On one hand I prefer the new "Another git process" message for when we don't have a PID lockfile. But on the other hand, I think the "If it still fails, a git process may have crashed..." message is useful for users who may not be immediately aware of the consequences of simply removing the lockfile to continue.

I do think the original message is somewhat verbose, so maybe the change here in the non-PID case is worth doing. What are your thoughts?

Show 11 quoted lines
> +
> +		strbuf_release(&lock_path);
>  	} else
>  		strbuf_addf(buf, _("Unable to create '%s.lock': %s"),
>  			    absolute_path(path), strerror(err));
> @@ -207,6 +292,8 @@ int commit_lock_file(struct lock_file *lk)
>  {
>  	char *result_path = get_locked_file_path(lk);
>
> +	delete_tempfile(&lk->pid_tempfile);
> +

Do we want to wait to delete the PID file until after we know that we successfully committed the lockfile?

> +/* Maximum length for PID file content */
> +#define LOCK_PID_MAXLEN 32

Makes sense ;-). This should be plenty of space, since on my system maximum PID value is 2^22:

    $ cat /proc/sys/kernel/pid_max
    4194304
Show 9 quoted lines
> +test_expect_success 'PID info file cleaned up on successful operation when enabled' '
> +	git init repo4 &&
> +	(
> +		cd repo4 &&
> +		echo content >file &&
> +		env GIT_LOCK_PID_INFO=1 git add file &&
> +		# After successful add, no lock or PID files should exist
> +		! test -f .git/index.lock &&
> +		! test -f .git/index.lock.pid
These should be:
    test_path_is_missing .git/index.lock &&
    test_path_is_missing .git/index.lock.pid
instead of bare "! test -f"'s.

Thanks, Taylor

Previous: Jeff KingNext: Paulo Casaretto via GitGitGadget
Message 10 of 36 in “lockfile: add PID file for debugging stale locks”
  1. lockfile: add PID file for debugging stale locksPaulo Casaretto via GitGitGadget, Dec 2, 2025
  2. D. Ben KnobleDec 2, 2025
  3. Torsten BögershausenDec 3, 2025
  4. Jeff KingDec 3, 2025
  5. Junio C HamanoDec 3, 2025
  6. Jeff KingDec 3, 2025
  7. Taylor BlauDec 3, 2025
  8. Patrick SteinhardtDec 5, 2025
  9. Jeff KingDec 5, 2025
  10. Taylor BlauDec 3, 2025
  11. lockfile: add PID file for debugging stale locksPaulo Casaretto via GitGitGadget, Dec 17, 2025
  12. Junio C HamanoDec 18, 2025
  13. Junio C HamanoDec 18, 2025
  14. Junio C HamanoDec 18, 2025
  15. Ben KnobleDec 18, 2025
  16. Patrick SteinhardtDec 18, 2025
  17. lockfile: add PID file for debugging stale locksPaulo Casaretto via GitGitGadget, Dec 24, 2025
  18. Junio C HamanoDec 25, 2025
  19. Jeff KingDec 27, 2025
  20. Patrick SteinhardtJan 5, 2026
  21. lockfile: add PID file for debugging stale locksPaulo Casaretto via GitGitGadget, Jan 7, 2026
  22. Junio C HamanoJan 8, 2026
  23. D. Ben KnobleJan 8, 2026
  24. lockfile: add PID file for debugging stale locksPaulo Casaretto via GitGitGadget, Jan 20, 2026
  25. Junio C HamanoJan 20, 2026
  26. Jeff KingJan 21, 2026
  27. Eric SunshineJan 21, 2026
  28. Johannes SixtJan 21, 2026
  29. Jeff KingJan 21, 2026
  30. Junio C HamanoJan 21, 2026
  31. Jeff KingJan 21, 2026
  32. Junio C HamanoJan 21, 2026
  33. lockfile: add PID file for debugging stale locksPaulo Casaretto via GitGitGadget, Jan 22, 2026
  34. Junio C HamanoJan 22, 2026
  35. Patrick SteinhardtFeb 6, 2026
  36. Junio C HamanoFeb 6, 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.