Repository navigation
Fix index.lock contention caused by git update-index on every FSEvents notification - #547
Merged
Merged
Conversation
Contributor
|
I can't reproduce the issue, that's why I can't verify that this fixes the issue. Test plan seems to be wrong
There is no index.lock |
Collaborator
Author
|
Thank you - I will try to improve the tests around this issue, because the current workflow is a bit tricky to perform. |
…s notification The commit view triggered git update-index --refresh on every file-system event via the FSEvents -> PBGitRepositoryEventNotification -> refresh chain. This held .git/index.lock for the duration of each run, causing concurrent git operations (git add, git commit, git stash, etc.) from terminals or editors to fail with "Unable to create index.lock: File exists". - Remove git update-index --refresh from the PBGitIndex refresh path; the remaining reads (diff-index, diff-files, ls-files) are read-only and never acquire the index lock - Add -refreshStatCache, which runs update-index --refresh as a separate fire-and-forget call followed by a refresh, to clear phantom stat-dirty entries (files that appear modified due to mtime mismatch but have unchanged content) - Call refreshStatCache on NSApplicationDidBecomeActiveNotification so the stat cache is reconciled when the user switches back to GitX, rather than on every file-system event Fixes gitx#164
Collaborator
Author
#!/bin/bash
# Reproduce GitX index.lock contention bug.
#
# Two modes:
# --simulate self-contained: runs update-index in a tight loop to mimic old GitX
# (default) tests real GitX: open the printed repo path in GitX commit view, press Enter
#
# Design:
# - Large repo (500 files) so update-index --refresh takes longer -> wider lock window
# - Background writer loop modifies files every 0.2s to keep FSEvents firing continuously
# - Foreground loop hammers "git add" independently of the writer
# - Also polls .git/index.lock directly (catches contention even if git add retries)
set -euo pipefail
SIMULATE=0
ITERATIONS=500
[[ "${1:-}" == "--simulate" ]] && SIMULATE=1
REPO=$(mktemp -d)
WRITER_PID=""
GITX_SIM_PID=""
cleanup() {
[[ -n "$WRITER_PID" ]] && kill "$WRITER_PID" 2>/dev/null || true
[[ -n "$GITX_SIM_PID" ]] && kill "$GITX_SIM_PID" 2>/dev/null || true
rm -rf "$REPO"
}
trap cleanup EXIT
cd "$REPO"
git init -q
git config user.email "[email protected]"
git config user.name "Test"
echo "==> Creating 500-file repo (wider lock window for update-index)..."
for i in $(seq 1 500); do
echo "file $i" > "file_$i.txt"
done
git add .
git commit -q -m "init"
echo " Repo: $REPO"
echo ""
# Background writer: modifies files every 0.2s to keep FSEvents firing.
# Decoupled from the git add loop so FSEvents fires throughout the entire test.
start_writer() {
while true; do
for i in 1 50 100 200 300 400 500; do
echo "$RANDOM" > "file_$i.txt"
done
sleep 0.2
done
}
if [[ $SIMULATE -eq 1 ]]; then
echo "==> Simulating OLD GitX behavior (update-index in tight loop)..."
while true; do
git update-index -q --unmerged --ignore-missing --refresh 2>/dev/null
done &
GITX_SIM_PID=$!
start_writer &
WRITER_PID=$!
else
echo "==> Open this repo in GitX and switch to the Commit tab:"
echo " $REPO"
echo ""
echo " Press Enter when ready..."
read -r
start_writer &
WRITER_PID=$!
echo " File writer started (keeps FSEvents firing every 0.2s)"
fi
echo ""
echo "==> Running $ITERATIONS rapid 'git add' attempts..."
echo ""
LOCK_ERRORS=0
SUCCESS=0
for i in $(seq 1 $ITERATIONS); do
echo "change $i" > "file_1.txt"
ERR=$(git add file_1.txt 2>&1 || true)
if echo "$ERR" | grep -q "index.lock"; then
LOCK_ERRORS=$((LOCK_ERRORS + 1))
if [[ $LOCK_ERRORS -le 3 ]]; then
echo " [attempt $i] LOCK ERROR"
fi
else
SUCCESS=$((SUCCESS + 1))
fi
done
echo ""
echo "Results: $SUCCESS succeeded, $LOCK_ERRORS lock errors / $ITERATIONS attempts"
echo ""
if [[ $LOCK_ERRORS -gt 0 ]]; then
echo "FAIL: index.lock contention detected -- bug is present"
exit 1
else
echo "PASS: no lock contention detected"
echo " (run 3x to be confident -- race is timing-dependent)"
exit 0
fi |
Contributor
|
Thanks a lot for the fix and the script ! |
1 task done
This was referenced Sep 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Fixes #164
While GitX has a repo open in the commit view, every file-system event triggered
git update-index --refresh, which holds.git/index.lockfor its entire run. This caused concurrent git operations from terminals or editors (git add,git commit,git stash, etc.) to intermittently fail withUnable to create index.lock: File exists.git update-index --refreshfrom thePBGitIndexrefresh path; the remaining reads (diff-index,diff-files,ls-files) are read-only and never acquire the index lock-refreshStatCachewhich runsupdate-index --refreshas a fire-and-forget call (followed by a refresh) to clear phantom stat-dirty entriesrefreshStatCacheonNSApplicationDidBecomeActiveNotificationso the stat cache is reconciled when the user switches back to GitX, rather than on every file-system eventTest plan
Reproducing the bug requires commands that actually acquire
index.lock(e.g.git add) and a repo large enough thatupdate-index --refreshholds the lock for a measurable window.Reproduce on unpatched build:
git addloop:With the unpatched binary, expect several
LOCK ERRORlines. With the fix applied, expect zero.Verify stat-cache refresh still works:
touch file_1.txt) so its mtime drifts