mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-20 11:52:20 +02:00
fix(gstack-memorable): stale-lock takeover by atomic rename; comments match behaviour
Two contenders that both saw a stale lock could both reclaim it with rm -rf; the settings-hook idiom (mv to a private name, exactly one winner) is used instead. The hook-manager fallback comment now says every verb falls back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
af72e3d4ff
commit
abf5d96400
+13
-5
@@ -59,7 +59,9 @@ if [ ! -x "$CANONICAL_GSTACK_ROOT/bin/gstack-session-update" ] \
|
|||||||
fi
|
fi
|
||||||
HOOK_CMD_PATH="$CANONICAL_GSTACK_ROOT/$HOOK_REL"
|
HOOK_CMD_PATH="$CANONICAL_GSTACK_ROOT/$HOOK_REL"
|
||||||
# Mutations go through the CANONICAL hook manager so the code that registers
|
# Mutations go through the CANONICAL hook manager so the code that registers
|
||||||
# is the code that will run; status falls back to this tree's copy for reads.
|
# is the code that will run. Every verb falls back to this tree's copy when the
|
||||||
|
# canonical one is missing (enable cannot get past compat_check then; disable
|
||||||
|
# and status must still work against a half-removed install).
|
||||||
SETTINGS_HOOK="$CANONICAL_GSTACK_ROOT/bin/gstack-settings-hook"
|
SETTINGS_HOOK="$CANONICAL_GSTACK_ROOT/bin/gstack-settings-hook"
|
||||||
[ -x "$SETTINGS_HOOK" ] || SETTINGS_HOOK="$SCRIPT_DIR/gstack-settings-hook"
|
[ -x "$SETTINGS_HOOK" ] || SETTINGS_HOOK="$SCRIPT_DIR/gstack-settings-hook"
|
||||||
EGRESS_BIN="$CANONICAL_GSTACK_ROOT/bin/gstack-egress"
|
EGRESS_BIN="$CANONICAL_GSTACK_ROOT/bin/gstack-egress"
|
||||||
@@ -101,7 +103,7 @@ _lock_release() {
|
|||||||
}
|
}
|
||||||
_lock_acquire() {
|
_lock_acquire() {
|
||||||
mkdir -p "$STATE_DIR/locks" 2>/dev/null || { _err "cannot create $STATE_DIR/locks"; return 5; }
|
mkdir -p "$STATE_DIR/locks" 2>/dev/null || { _err "cannot create $STATE_DIR/locks"; return 5; }
|
||||||
local tries=0 mtime now
|
local tries=0 mtime now stale
|
||||||
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
||||||
tries=$((tries + 1))
|
tries=$((tries + 1))
|
||||||
# Staleness from the directory's own mtime (set atomically by the holder's
|
# Staleness from the directory's own mtime (set atomically by the holder's
|
||||||
@@ -112,7 +114,13 @@ _lock_acquire() {
|
|||||||
mtime="$(stat -c %Y "$LOCK_DIR" 2>/dev/null || stat -f %m "$LOCK_DIR" 2>/dev/null || echo "")"
|
mtime="$(stat -c %Y "$LOCK_DIR" 2>/dev/null || stat -f %m "$LOCK_DIR" 2>/dev/null || echo "")"
|
||||||
case "$mtime" in *[!0-9]*|"") mtime="" ;; esac
|
case "$mtime" in *[!0-9]*|"") mtime="" ;; esac
|
||||||
now="$(date +%s)"
|
now="$(date +%s)"
|
||||||
if [ -n "$mtime" ] && [ $((now - mtime)) -gt "$LOCK_STALE_S" ]; then rm -rf "$LOCK_DIR"; continue; fi
|
if [ -n "$mtime" ] && [ $((now - mtime)) -gt "$LOCK_STALE_S" ]; then
|
||||||
|
# Atomic rename: exactly one contender reclaims a stale lock; the loser
|
||||||
|
# loops and re-contends against the winner's fresh mkdir.
|
||||||
|
stale="$LOCK_DIR.stale.$$-$RANDOM"
|
||||||
|
if mv "$LOCK_DIR" "$stale" 2>/dev/null; then rm -rf "$stale" 2>/dev/null || true; fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
if [ "$tries" -ge "$LOCK_TRIES" ]; then _err "another gstack-memorable is running (lock $LOCK_DIR); try again"; return 5; fi
|
if [ "$tries" -ge "$LOCK_TRIES" ]; then _err "another gstack-memorable is running (lock $LOCK_DIR); try again"; return 5; fi
|
||||||
sleep "$LOCK_SLEEP"
|
sleep "$LOCK_SLEEP"
|
||||||
done
|
done
|
||||||
@@ -205,7 +213,7 @@ enable_bridge() {
|
|||||||
[ -x "$SETTINGS_HOOK" ] || { _err "missing hook manager: $SETTINGS_HOOK"; return 1; }
|
[ -x "$SETTINGS_HOOK" ] || { _err "missing hook manager: $SETTINGS_HOOK"; return 1; }
|
||||||
[ -x "$GSTACK_CONFIG" ] || { _err "missing $GSTACK_CONFIG"; return 1; }
|
[ -x "$GSTACK_CONFIG" ] || { _err "missing $GSTACK_CONFIG"; return 1; }
|
||||||
if [ "$IS_WINDOWS" -eq 1 ]; then
|
if [ "$IS_WINDOWS" -eq 1 ]; then
|
||||||
_err "Windows is not supported by the Memorable bridge yet (no way to contain the vendor process); tracked in TODOS.md D21"
|
_err "Windows is not supported by the Memorable bridge yet (no way to contain the vendor process); tracked in TODOS.md: Windows support for the Memorable bridge (D21)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
vendor="$(resolve_memorable)" || { _err "Memorable CLI not found (checked $RESOLUTION_ORDER). Install it yourself: npm i -g memorable-cli. gstack never installs it."; return 1; }
|
vendor="$(resolve_memorable)" || { _err "Memorable CLI not found (checked $RESOLUTION_ORDER). Install it yourself: npm i -g memorable-cli. gstack never installs it."; return 1; }
|
||||||
@@ -337,7 +345,7 @@ status_bridge() {
|
|||||||
esac
|
esac
|
||||||
if [ "$gate" = "on" ] && [ "$REG_STATE" = "none" ]; then echo "mismatch: gate on, no hook registered (run: gstack-memorable enable)"; fi
|
if [ "$gate" = "on" ] && [ "$REG_STATE" = "none" ]; then echo "mismatch: gate on, no hook registered (run: gstack-memorable enable)"; fi
|
||||||
if [ "$gate" != "on" ] && { [ "$REG_STATE" = "gstack" ] || [ "$REG_STATE" = "both" ]; }; then echo "mismatch: hook registered but gate is '$gate' (hook is inert; run: gstack-memorable disable to remove it)"; fi
|
if [ "$gate" != "on" ] && { [ "$REG_STATE" = "gstack" ] || [ "$REG_STATE" = "both" ]; }; then echo "mismatch: hook registered but gate is '$gate' (hook is inert; run: gstack-memorable disable to remove it)"; fi
|
||||||
if [ "$IS_WINDOWS" -eq 1 ]; then echo "platform: Windows is not supported by this bridge yet (TODOS.md D21)"; fi
|
if [ "$IS_WINDOWS" -eq 1 ]; then echo "platform: Windows is not supported by this bridge yet (TODOS.md: Windows support for the Memorable bridge, D21)"; fi
|
||||||
if [ -x "$EGRESS_BIN" ] && command -v bun >/dev/null 2>&1; then
|
if [ -x "$EGRESS_BIN" ] && command -v bun >/dev/null 2>&1; then
|
||||||
# Count the filtered array, not a formatting artefact of the pretty-printed JSON.
|
# Count the filtered array, not a formatting artefact of the pretty-printed JSON.
|
||||||
n="$("$EGRESS_BIN" list --sink "$SINK" --json 2>/dev/null | bun -e 'const a=JSON.parse(require("fs").readFileSync(0,"utf8")||"[]");console.log(Array.isArray(a)?a.length:0)' 2>/dev/null)"
|
n="$("$EGRESS_BIN" list --sink "$SINK" --json 2>/dev/null | bun -e 'const a=JSON.parse(require("fs").readFileSync(0,"utf8")||"[]");console.log(Array.isArray(a)?a.length:0)' 2>/dev/null)"
|
||||||
|
|||||||
Reference in New Issue
Block a user