Two xce-build.sh runs against the same build dir raced: one run's
setup_build rm -rf hit files the other was writing ('Directory not
empty'), or deleted a binary the other had just built and tested.
xce-build.sh now takes an exclusive per-build-dir lock right after cd-ing
into src/ and holds it until it exits; a second run against the same dir
waits, runs against different dirs are unaffected. The lock
(src/tools/build-lock.sh) is an mkdir lock holding the owner PID, outside
the build dir, keyed by a hash of its absolute path; it needs only
coreutils (the nix dev shell has no flock) and takes over a lock whose
owner died. src/tools/test-build-lock.sh tests it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
59 lines
2.3 KiB
Bash
59 lines
2.3 KiB
Bash
# build-lock.sh — exclusive per-build-dir lock for xce-build.sh (bug-1604).
|
|
#
|
|
# Sourced, not executed. Two xce-build.sh runs against the SAME build dir used
|
|
# to race: one run's `rm -rf $BUILD_DIR` hit files the other was writing
|
|
# ("Directory not empty") or silently deleted a binary the other had just
|
|
# built and tested. The lock serializes them for the whole run (wipe, setup,
|
|
# compile, tests); runs against DIFFERENT build dirs stay concurrent.
|
|
#
|
|
# The lock is a directory created with mkdir (atomic; needs nothing beyond
|
|
# coreutils, unlike flock(1), which the nix dev shell does not provide),
|
|
# holding the owner's PID. It lives outside the build dir -- the wipe would
|
|
# delete it -- under $XCE_BUILD_LOCK_ROOT (default $TMPDIR or /tmp), named
|
|
# after a hash of the build dir's absolute path, so separate checkouts never
|
|
# share a lock. A lock whose owner is no longer running (a build killed with
|
|
# SIGKILL) is taken over.
|
|
#
|
|
# build_lock_acquire <build_dir> blocks until held
|
|
# build_lock_release releases it (safe to call twice)
|
|
#
|
|
# Tunables: XCE_BUILD_LOCK_ROOT, XCE_BUILD_LOCK_POLL_SECS (default 2).
|
|
|
|
BUILD_LOCK_DIR=""
|
|
|
|
build_lock_path() {
|
|
local abs sum
|
|
abs="$(realpath -m -- "$1")"
|
|
sum="$(printf '%s' "$abs" | cksum | cut -d' ' -f1)"
|
|
printf '%s/xce-build-%s.lock' "${XCE_BUILD_LOCK_ROOT:-${TMPDIR:-/tmp}}" "$sum"
|
|
}
|
|
|
|
build_lock_acquire() {
|
|
local lock owner waited=0 poll="${XCE_BUILD_LOCK_POLL_SECS:-2}"
|
|
|
|
lock="$(build_lock_path "$1")"
|
|
while ! mkdir "$lock" 2>/dev/null; do
|
|
owner="$(cat "$lock/pid" 2>/dev/null || true)"
|
|
if [ -n "$owner" ] && ! kill -0 "$owner" 2>/dev/null; then
|
|
# The owner died holding it. If two waiters take over the same
|
|
# stale lock at once, mkdir below still lets only one of them in.
|
|
echo "=== Build lock for $1 was left by PID $owner, which is gone -- taking it over ===" >&2
|
|
rm -rf -- "$lock"
|
|
continue
|
|
fi
|
|
if [ "$waited" = 0 ]; then
|
|
echo "=== Waiting for another xce-build.sh run (PID ${owner:-?}) to finish with $1 ... ===" >&2
|
|
waited=1
|
|
fi
|
|
sleep "$poll"
|
|
done
|
|
echo "${BASHPID:-$$}" > "$lock/pid"
|
|
BUILD_LOCK_DIR="$lock"
|
|
}
|
|
|
|
build_lock_release() {
|
|
if [ -n "$BUILD_LOCK_DIR" ]; then
|
|
rm -rf -- "$BUILD_LOCK_DIR"
|
|
BUILD_LOCK_DIR=""
|
|
fi
|
|
}
|