Skip to content

Commit cfbf3a8

Browse files
committed
lockfile: add PID file for debugging stale locks
When a lock file is held, it can be helpful to know which process owns it, especially when debugging stale locks left behind by crashed processes. Add an optional feature that creates a companion PID file alongside each lock file, containing the PID of the lock holder. For a lock file "foo.lock", the PID file is named "foo-pid.lock". The 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. When a lock conflict occurs, the code checks for an existing PID file and, if found, uses kill(pid, 0) to determine if the process is still running. This allows providing context-aware error messages: Lock is held by process 12345. Wait for it to finish, or remove the lock file to continue. Or for a stale lock: Lock was held by process 12345, which is no longer running. Remove the stale lock file to continue. The feature is controlled via core.lockfilePid configuration, which accepts per-component values similar to core.fsync: - none: Disable for all components (default) - all: Enable for all components - index, config, refs, commit-graph, midx, shallow, gc, other: Enable for specific components Multiple components can be combined with commas: git config core.lockfilePid index,config Each lock file call site specifies which component it belongs to, allowing users fine-grained control over which locks create PID files. Existing PID files are always read when displaying lock errors, regardless of the core.lockfilePid setting. This ensures helpful diagnostics even when the feature was previously enabled and later disabled. Signed-off-by: Paulo Casaretto <pcasaretto@gmail.com>
1 parent 6ab38b7 commit cfbf3a8

29 files changed

+522
-75
lines changed

Documentation/config/core.adoc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,31 @@ confusion unless you know what you are doing (e.g. you are creating a
348348
read-only snapshot of the same index to a location different from the
349349
repository's usual working tree).
350350

351+
core.lockfilePid::
352+
A comma-separated list of components for which Git should create
353+
a PID file alongside the lock file. When a lock acquisition fails
354+
and a PID file exists, Git can provide additional diagnostic
355+
information about the process holding the lock, including whether
356+
it is still running.
357+
+
358+
This feature is disabled by default. You can enable it for specific
359+
components or use `all` to enable for all components.
360+
+
361+
* `none` disables PID file creation for all components.
362+
* `index` creates PID files for index lock operations.
363+
* `config` creates PID files for config file lock operations.
364+
* `refs` creates PID files for reference lock operations.
365+
* `commit-graph` creates PID files for commit-graph lock operations.
366+
* `midx` creates PID files for multi-pack-index lock operations.
367+
* `shallow` creates PID files for shallow file lock operations.
368+
* `gc` creates PID files for garbage collection lock operations.
369+
* `other` creates PID files for other miscellaneous lock operations.
370+
* `all` enables PID file creation for all components.
371+
+
372+
The PID file is named by inserting `-pid` before the `.lock` suffix.
373+
For example, if the lock file is `index.lock`, the PID file will be
374+
`index-pid.lock`.
375+
351376
core.logAllRefUpdates::
352377
Enable the reflog. Updates to a ref <ref> is logged to the file
353378
"`$GIT_DIR/logs/<ref>`", by appending the new and old

Documentation/git.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,16 @@ be in the future).
10101010
the background which do not want to cause lock contention with
10111011
other operations on the repository. Defaults to `1`.
10121012

1013+
`GIT_LOCK_PID_INFO`::
1014+
If this Boolean environment variable is set to `1`, Git will create
1015+
a `.lock.pid` file alongside each lock file containing the PID of the
1016+
process that created the lock. This information is displayed in error
1017+
messages when a lock conflict occurs, making it easier to identify
1018+
stale locks or debug locking issues. The PID files are automatically
1019+
cleaned up via signal and atexit handlers; however, if a process is
1020+
terminated abnormally (e.g., SIGKILL), the file may remain as a stale
1021+
indicator. Disabled by default.
1022+
10131023
`GIT_REDIRECT_STDIN`::
10141024
`GIT_REDIRECT_STDOUT`::
10151025
`GIT_REDIRECT_STDERR`::

apply.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4173,7 +4173,8 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)
41734173
}
41744174
}
41754175

4176-
hold_lock_file_for_update(&lock, state->fake_ancestor, LOCK_DIE_ON_ERROR);
4176+
hold_lock_file_for_update(&lock, state->fake_ancestor, LOCK_DIE_ON_ERROR,
4177+
LOCKFILE_PID_OTHER);
41774178
res = write_locked_index(&result, &lock, COMMIT_LOCK);
41784179
discard_index(&result);
41794180

@@ -4830,7 +4831,8 @@ static int apply_patch(struct apply_state *state,
48304831
if (state->index_file)
48314832
hold_lock_file_for_update(&state->lock_file,
48324833
state->index_file,
4833-
LOCK_DIE_ON_ERROR);
4834+
LOCK_DIE_ON_ERROR,
4835+
LOCKFILE_PID_INDEX);
48344836
else
48354837
repo_hold_locked_index(state->repo, &state->lock_file,
48364838
LOCK_DIE_ON_ERROR);

builtin/commit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
540540
path = repo_git_path(the_repository, "next-index-%"PRIuMAX,
541541
(uintmax_t) getpid());
542542
hold_lock_file_for_update(&false_lock, path,
543-
LOCK_DIE_ON_ERROR);
543+
LOCK_DIE_ON_ERROR, LOCKFILE_PID_OTHER);
544544

545545
create_base_index(current_head);
546546
add_remove_files(&partial);

builtin/credential-store.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ static void rewrite_credential_file(const char *fn, struct credential *c,
6767
int timeout_ms = 1000;
6868

6969
repo_config_get_int(the_repository, "credentialstore.locktimeoutms", &timeout_ms);
70-
if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0)
70+
if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms,
71+
LOCKFILE_PID_CONFIG) < 0)
7172
die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms);
7273
if (extra)
7374
print_line(extra);

builtin/difftool.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,8 @@ static int run_dir_diff(struct repository *repo,
636636
struct lock_file lock = LOCK_INIT;
637637
strbuf_reset(&buf);
638638
strbuf_addf(&buf, "%s/wtindex", tmpdir.buf);
639-
if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
639+
if (hold_lock_file_for_update(&lock, buf.buf, 0,
640+
LOCKFILE_PID_OTHER) < 0 ||
640641
write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
641642
ret = error("could not write %s", buf.buf);
642643
goto finish;

builtin/fast-import.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1734,7 +1734,8 @@ static void dump_marks(void)
17341734
return;
17351735
}
17361736

1737-
if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) {
1737+
if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0,
1738+
LOCKFILE_PID_OTHER) < 0) {
17381739
failure |= error_errno(_("unable to write marks file %s"),
17391740
export_marks_file);
17401741
return;

builtin/gc.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
749749

750750
pidfile_path = repo_git_path(the_repository, "gc.pid");
751751
fd = hold_lock_file_for_update(&lock, pidfile_path,
752-
LOCK_DIE_ON_ERROR);
752+
LOCK_DIE_ON_ERROR, LOCKFILE_PID_GC);
753753
if (!force) {
754754
static char locking_host[HOST_NAME_MAX + 1];
755755
static char *scan_fmt;
@@ -1017,7 +1017,7 @@ int cmd_gc(int argc,
10171017
if (daemonized) {
10181018
char *path = repo_git_path(the_repository, "gc.log");
10191019
hold_lock_file_for_update(&log_lock, path,
1020-
LOCK_DIE_ON_ERROR);
1020+
LOCK_DIE_ON_ERROR, LOCKFILE_PID_GC);
10211021
dup2(get_lock_file_fd(&log_lock), 2);
10221022
atexit(process_log_file_at_exit);
10231023
free(path);
@@ -1797,7 +1797,8 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
17971797
struct repository *r = the_repository;
17981798
char *lock_path = xstrfmt("%s/maintenance", r->objects->sources->path);
17991799

1800-
if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
1800+
if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF,
1801+
LOCKFILE_PID_GC) < 0) {
18011802
/*
18021803
* Another maintenance command is running.
18031804
*
@@ -2561,7 +2562,8 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
25612562
lock_file_timeout_ms = 150;
25622563

25632564
fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
2564-
lock_file_timeout_ms);
2565+
lock_file_timeout_ms,
2566+
LOCKFILE_PID_GC);
25652567

25662568
/*
25672569
* Does this file already exist? With the intended contents? Is it
@@ -3372,7 +3374,8 @@ static int update_background_schedule(const struct maintenance_start_opts *opts,
33723374
struct lock_file lk;
33733375
char *lock_path = xstrfmt("%s/schedule", the_repository->objects->sources->path);
33743376

3375-
if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
3377+
if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF,
3378+
LOCKFILE_PID_GC) < 0) {
33763379
if (errno == EEXIST)
33773380
error(_("unable to create '%s.lock': %s.\n\n"
33783381
"Another scheduled git-maintenance(1) process seems to be running in this\n"

builtin/sparse-checkout.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ static int write_patterns_and_update(struct repository *repo,
341341
if (safe_create_leading_directories(repo, sparse_filename))
342342
die(_("failed to create directory for sparse-checkout file"));
343343

344-
hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR);
344+
hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR,
345+
LOCKFILE_PID_OTHER);
345346

346347
result = update_working_directory(repo, pl);
347348
if (result) {

bundle.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,8 @@ int create_bundle(struct repository *r, const char *path,
520520
bundle_fd = 1;
521521
else
522522
bundle_fd = hold_lock_file_for_update(&lock, path,
523-
LOCK_DIE_ON_ERROR);
523+
LOCK_DIE_ON_ERROR,
524+
LOCKFILE_PID_OTHER);
524525

525526
if (version == -1)
526527
version = min_version;

0 commit comments

Comments
 (0)