#!/bin/bash # test-build-lock.sh — bug-1604: concurrent xce-build.sh runs against the same # build dir must not race on its rm -rf / meson setup. # # Exercises src/tools/build-lock.sh (sourced by xce-build.sh) directly, so no # real build is needed: # 1. a second acquire of the SAME build dir waits until the first releases # 2. a lock left by a dead process is taken over, not waited on forever # 3. DIFFERENT build dirs never block each other # 4. xce-build.sh takes the lock before setup_build's rm -rf # # Usage: # bash src/tools/test-build-lock.sh set -u TOOLS=$(cd "$(dirname "$0")" && pwd) PASS=0 FAIL=0 green() { printf '\033[32m%s\033[0m\n' "$*"; } red() { printf '\033[31m%s\033[0m\n' "$*"; } pass() { green " PASS: $*"; PASS=$((PASS+1)); } fail() { red " FAIL: $*"; FAIL=$((FAIL+1)); } WORK=$(mktemp -d) trap 'rm -rf "$WORK"' EXIT export XCE_BUILD_LOCK_ROOT="$WORK/locks" export XCE_BUILD_LOCK_POLL_SECS=0.1 mkdir -p "$XCE_BUILD_LOCK_ROOT" if [ ! -f "$TOOLS/build-lock.sh" ]; then fail "src/tools/build-lock.sh does not exist" echo "Results: ${PASS} passed, ${FAIL} failed" exit 1 fi # A holder: takes the lock on $1, records when it has it, holds it for $2 s, # records the release, exits. holder() { ( . "$TOOLS/build-lock.sh" build_lock_acquire "$1" >/dev/null 2>&1 echo "$3 got $(date +%s.%N)" >> "$WORK/events" sleep "$2" echo "$3 released $(date +%s.%N)" >> "$WORK/events" build_lock_release ) } event_time() { awk -v w="$1 $2" '$1" "$2 == w {print $3}' "$WORK/events"; } echo "── 1. same build dir: the second acquire waits for the first ──" : > "$WORK/events" holder "$WORK/b1" 1 A & sleep 0.3 holder "$WORK/b1" 0 B & wait a_rel=$(event_time A released) b_got=$(event_time B got) if [ -n "$a_rel" ] && [ -n "$b_got" ] && \ awk -v a="$a_rel" -v b="$b_got" 'BEGIN { exit !(b >= a) }'; then pass "B got the lock only after A released it" else fail "B got the lock while A still held it (A released=$a_rel, B got=$b_got)" fi echo "── 2. a lock left by a dead process is taken over ──" : > "$WORK/events" ( . "$TOOLS/build-lock.sh" build_lock_acquire "$WORK/b2" >/dev/null 2>&1 # exit WITHOUT releasing, like a build killed with SIGKILL ) start=$(date +%s) holder "$WORK/b2" 0 C & cpid=$! ( sleep 10; kill "$cpid" 2>/dev/null ) & watchdog=$! wait "$cpid" 2>/dev/null kill "$watchdog" 2>/dev/null if [ -n "$(event_time C got)" ] && [ $(( $(date +%s) - start )) -lt 10 ]; then pass "a stale lock from a dead owner was taken over" else fail "waited on a lock whose owner is dead" fi echo "── 3. different build dirs do not block each other ──" : > "$WORK/events" holder "$WORK/b3" 2 D & sleep 0.3 start=$(date +%s.%N) holder "$WORK/b4" 0 E & wait e_got=$(event_time E got) if [ -n "$e_got" ] && awk -v s="$start" -v e="$e_got" 'BEGIN { exit !(e - s < 1.5) }'; then pass "a lock on another build dir was granted immediately" else fail "a lock on another build dir waited for an unrelated holder" fi echo "── 4. xce-build.sh takes the lock before setup_build's rm -rf ──" lock_line=$(grep -n '^build_lock_acquire ' "$TOOLS/xce-build.sh" | head -1 | cut -d: -f1) setup_line=$(grep -n '^setup_build() {' "$TOOLS/xce-build.sh" | head -1 | cut -d: -f1) first_call=$(grep -n '^ *setup_build$' "$TOOLS/xce-build.sh" | head -1 | cut -d: -f1) if grep -q 'build-lock.sh' "$TOOLS/xce-build.sh" && [ -n "$lock_line" ] && \ [ -n "$first_call" ] && [ "$lock_line" -lt "$first_call" ]; then pass "xce-build.sh acquires the build-dir lock (line $lock_line) before the first setup_build call (line $first_call)" else fail "xce-build.sh does not acquire the build-dir lock before setup_build (lock=$lock_line setup_build()=$setup_line first call=$first_call)" fi echo "" echo "Results: ${PASS} passed, ${FAIL} failed" [ "$FAIL" -eq 0 ]