From fc1fd27a3f997f7314cbe8481ffb5a618916e08d Mon Sep 17 00:00:00 2001 From: gmer4lfe Date: Sun, 24 May 2026 21:02:07 -0400 Subject: [PATCH] fix(common): lock registry prevents EXIT trap overwrite orphaning locks Scripts calling acquire_lock followed by acquire_rsync_lock (rsync.sh) or a custom EXIT trap (resource_watchdog.sh) would overwrite the lock release trap, leaving the first lock file behind on exit. Replace per-acquire trap with a _LOCK_FILES registry. _register_lock() appends each lock file to the array and sets a single _release_all_locks trap. All acquired locks release together on exit regardless of how many traps are subsequently set or overwritten. resource_watchdog.sh: chain _release_all_locks into both its custom trap and the state-persisted disarm path so the lock releases in all exit paths. --- Watchdogs/resource_watchdog.sh | 4 ++-- common.sh | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Watchdogs/resource_watchdog.sh b/Watchdogs/resource_watchdog.sh index 4729c7b..c58c7b1 100755 --- a/Watchdogs/resource_watchdog.sh +++ b/Watchdogs/resource_watchdog.sh @@ -151,7 +151,7 @@ _rw_trap_restart_stopped() { fi done } -trap _rw_trap_restart_stopped EXIT +trap "_release_all_locks; _rw_trap_restart_stopped" EXIT # ============================================================================================== # ━━━ State Helpers ━━━ @@ -580,6 +580,6 @@ fi # ============================================================================================== rm_state_set "rm_paused_containers" "$PAUSED_LIST" rm_state_set "rm_stopped_containers" "$STOPPED_LIST" -trap - EXIT # state persisted — stopped containers recorded, trap no longer needed +trap "_release_all_locks" EXIT # state persisted — disable restart trap, keep lock cleanup # Touch state file each run so docker_watchdog stale guard sees fresh mtime touch "$RW_STATE_FILE" 2>/dev/null diff --git a/common.sh b/common.sh index 86262b7..39ce672 100755 --- a/common.sh +++ b/common.sh @@ -1253,6 +1253,21 @@ RSYNC_MAX_CONCURRENT=3 LOCK_WARN_AGE=300 # seconds — warn if lock older than this (5min default) LOCK_WAIT_TIMEOUT=30 # seconds — how long "wait" mode waits before giving up +# Registry of all lock files acquired by this process — released together on exit. +# Prevents the single-trap-per-acquire problem: each new acquire_lock/acquire_rsync_lock +# call used to overwrite the EXIT trap, orphaning the previous lock file. +declare -ga _LOCK_FILES=() +_release_all_locks() { + local lf + for lf in "${_LOCK_FILES[@]}"; do + [[ -f "$lf" ]] && rm -f "$lf" + done +} +_register_lock() { + _LOCK_FILES+=("$1") + trap '_release_all_locks' EXIT +} + # Internal — script name used as lock identifier _lock_name() { basename "${BASH_SOURCE[1]:-$0}" .sh @@ -1344,7 +1359,7 @@ acquire_lock() { # Acquire lock — store PID:scriptname to prevent PID reuse false positives echo "$$:$script_name" > "$lockfile" - trap "_release_on_exit '$lockfile'" EXIT + _register_lock "$lockfile" log "$ICON_LOCK Lock acquired: $script_name (PID $$)" } @@ -1402,7 +1417,7 @@ acquire_rsync_lock() { # Acquire profile lock and increment counter echo "$$:rsync_${profile}" > "$profile_lock" echo $(( current_count + 1 )) > "$RSYNC_COUNT_FILE" - trap "_release_rsync_on_exit '$profile_lock'" EXIT + _register_lock "$profile_lock" log "$ICON_LOCK rsync lock acquired: profile '$profile' (PID $$, active: $(( current_count + 1 ))/$RSYNC_MAX_CONCURRENT)" }