Files
multi-agent-mux/.agents/skills/multi-agent-mux-loop/scripts/loop_lock.sh
T

169 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# loop_lock.sh — Atomic race-free loop lock acquisition and marker ownership (O-2)
# Manages .mam/loop-guard-active with atomic hard links and pid+lstart identity validation.
MAM_LOCK_STEAL_RETRIES="${MAM_LOCK_STEAL_RETRIES:-3}"
mam_lstart() {
local pid="${1:-$$}"
ps -p "$pid" -o lstart= 2>/dev/null | tr -s ' ' | sed 's/^ *//;s/ *$//'
}
_mam_lock_field() {
local file="$1" key="$2"
if [ ! -f "$file" ]; then
return 1
fi
grep "^${key}=" "$file" 2>/dev/null | head -n 1 | cut -d'=' -f2- || true
}
_mam_lock_holder_state() {
local marker="$1"
if [ ! -f "$marker" ]; then
printf 'stale'
return 0
fi
local rec_pid rec_lstart cur_lstart own_lstart
rec_pid=$(_mam_lock_field "$marker" "pid")
rec_lstart=$(_mam_lock_field "$marker" "lstart")
if [ -z "$rec_pid" ] || ! [[ "$rec_pid" =~ ^[0-9]+$ ]]; then
printf 'stale'
return 0
fi
own_lstart=$(mam_lstart "$$")
if [ -z "$own_lstart" ]; then
printf 'unknown'
return 0
fi
cur_lstart=$(mam_lstart "$rec_pid")
if [ -z "$cur_lstart" ]; then
printf 'stale'
return 0
fi
if [ -n "$rec_lstart" ]; then
if [ "$cur_lstart" != "$rec_lstart" ]; then
printf 'stale'
return 0
fi
fi
printf 'live'
return 0
}
_mam_lock_publish() {
local marker="$1"
local parent_dir
parent_dir=$(dirname "$marker")
mkdir -p "$parent_dir" 2>/dev/null || true
local tmp_marker="${marker}.tmp.${$}.${RANDOM:-0}"
local lstart_val
lstart_val=$(mam_lstart "$$")
printf 'pid=%s\nlstart=%s\nstarted=%s\n' \
"$$" "$lstart_val" "$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date)" > "$tmp_marker"
local rc=0
ln "$tmp_marker" "$marker" 2>/dev/null || rc=$?
rm -f "$tmp_marker" 2>/dev/null || true
return $rc
}
mam_acquire_loop_lock() {
local marker="${1:-}"
if [ -z "$marker" ]; then
return 2
fi
local steal_lock="${marker}.steal"
local retries=0
while [ "$retries" -le "$MAM_LOCK_STEAL_RETRIES" ]; do
# Upgrade path: clean up legacy directory at steal_lock if present
if [ -d "$steal_lock" ]; then
rmdir "$steal_lock" 2>/dev/null || true
fi
# Try publishing primary marker
if _mam_lock_publish "$marker"; then
return 0
fi
# Check primary marker holder state
local state
state=$(_mam_lock_holder_state "$marker")
if [ "$state" = "live" ]; then
return 3
fi
if [ "$state" = "unknown" ]; then
return 4
fi
# Primary marker is stale! Try publishing steal_lock
if _mam_lock_publish "$steal_lock"; then
if [ "$(_mam_lock_holder_state "$marker")" = "stale" ]; then
rm -f "$marker" 2>/dev/null || true
fi
rm -f "$steal_lock" 2>/dev/null || true
if _mam_lock_publish "$marker"; then
return 0
fi
fi
# Check steal_lock holder state
local steal_state
steal_state=$(_mam_lock_holder_state "$steal_lock")
case "$steal_state" in
live)
return 3
;;
unknown)
return 4
;;
stale)
rm -f "$steal_lock" 2>/dev/null || true
retries=$((retries + 1))
continue
;;
esac
sleep 1
retries=$((retries + 1))
done
return 3
}
mam_release_loop_lock() {
local marker="${1:-}"
if [ -z "$marker" ] || [ ! -f "$marker" ]; then
return 0
fi
local rec_pid rec_lstart cur_lstart
rec_pid=$(_mam_lock_field "$marker" "pid")
rec_lstart=$(_mam_lock_field "$marker" "lstart")
cur_lstart=$(mam_lstart "$$")
if [ "$rec_pid" = "$$" ] && [ "$rec_lstart" = "$cur_lstart" ]; then
rm -f "$marker" 2>/dev/null || true
return 0
fi
return 1
}
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
case "${1:-}" in
acquire) mam_acquire_loop_lock "${2:-}" ;;
release) mam_release_loop_lock "${2:-}" ;;
*) echo "Usage: $0 {acquire|release} <marker-path>" ; exit 1 ;;
esac
fi