fix: revalidate exiting demo anchors

This commit is contained in:
golem
2026-08-21 05:12:50 -06:00
parent a28ad76868
commit eb2fd5245a
2 changed files with 193 additions and 22 deletions
+163 -5
View File
@@ -8,6 +8,8 @@ TEST_ROOT=$(mktemp -d /tmp/uups-bank-process-test.XXXXXX)
PATH="$TEST_ROOT/bin:$PATH"
export DEMO_TERM_WAIT_ATTEMPTS=5
export DEMO_KILL_WAIT_ATTEMPTS=10
export DEMO_ANCHOR_RECHECK_ATTEMPTS=10
export DEMO_ANCHOR_RECHECK_INTERVAL=0.01
ANVIL_DEV_PHRASE='test test test test test test test test test test test junk'
ANVIL_EXACT_ARGS=(--host 127.0.0.1 --port 8545 --chain-id 31337 --mnemonic "$ANVIL_DEV_PHRASE")
declare -a TEST_IDENTITIES=()
@@ -17,7 +19,7 @@ STARTED_PID=
cleanup() {
local identity root relative
for identity in "${TEST_IDENTITIES[@]}"; do test_safe_stop "$identity"; done
for root in absent stale nonnumeric wrong-command false-anvil false-vite exact-anvil raw-capture reused atomic matching group mutated partial local-reset base-canonical base-active sentinel; do
for root in absent stale nonnumeric wrong-command false-anvil false-vite exact-anvil raw-capture reused atomic matching group term-refusal anchor-race mutated partial local-reset base-canonical base-active sentinel; do
for relative in \
.demo/anvil.pid .demo/anvil.start .demo/anvil.pgid .demo/anvil.log \
.demo/vite.pid .demo/vite.start .demo/vite.pgid .demo/vite.log \
@@ -32,7 +34,9 @@ cleanup() {
"$TEST_ROOT/$root" 2>/dev/null || true
done
rm -f -- "$TEST_ROOT/bin/anvil" "$TEST_ROOT/bin/vite" "$TEST_ROOT/bin/leaderless" \
"$TEST_ROOT/vite-child.pid" "$TEST_ROOT/leaderless-child.pid"
"$TEST_ROOT/vite-child.pid" "$TEST_ROOT/term-refusal-child.pid" "$TEST_ROOT/anchor-race-child.pid" \
"$TEST_ROOT/leaderless-child.pid" "$TEST_ROOT/vanished-signal.err" "$TEST_ROOT/unsignaled-signal.err" \
"$TEST_ROOT/term-refusal.err"
rmdir -- "$TEST_ROOT/bin" "$TEST_ROOT" 2>/dev/null || true
}
trap cleanup EXIT
@@ -167,7 +171,7 @@ assert_anvil_args_rejected() {
demo_stop_raw_launch "$root" anvil "$pid" "$start" "$pgid"
}
printf '1..21\n'
printf '1..26\n'
# Catches cleanup treating a missing record as an error or signaling an inferred PID.
root=$(make_root absent)
@@ -235,6 +239,80 @@ assert_anvil_args_rejected "$root" reordered \
--port 8545 --host 127.0.0.1 --chain-id 31337 --mnemonic "$ANVIL_DEV_PHRASE"
pass 'Anvil identity requires the exact complete launch argument tail'
# Catches treating one transient cmdline/exe read failure as a permanent identity mismatch.
setsid /bin/sleep 120 & recovered_anchor_pid=$!
track_process "$recovered_anchor_pid"
recovered_anchor_state='' recovered_anchor_start='' recovered_anchor_pgid='' recovered_anchor_sid=''
test_process_identity "$recovered_anchor_pid" recovered_anchor_state recovered_anchor_start \
recovered_anchor_pgid recovered_anchor_sid
[[ "$recovered_anchor_state" != Z && "$recovered_anchor_sid" == "$recovered_anchor_pid" ]] \
|| fail 'recovering anchor fixture was not a live session leader'
if (
anchor_command_attempt=0
demo_command_matches() {
anchor_command_attempt=$((anchor_command_attempt + 1))
((anchor_command_attempt > 1)) && return 0
return 2
}
demo_anchor_allows_signal "$ROOT" anvil "$recovered_anchor_pid" "$recovered_anchor_start" \
"$recovered_anchor_pgid" validated
); then
recovered_anchor_result=0
else
recovered_anchor_result=$?
fi
((recovered_anchor_result == 0)) || fail 'a transient anchor command-read failure was not retried'
assert_alive "$recovered_anchor_pid"
pass 'a transient anchor command-read failure recovers through exact revalidation'
# Catches failing shutdown when an unverifiable leader vanishes during bounded observation.
setsid /bin/sleep 120 & vanishing_anchor_pid=$!
track_process "$vanishing_anchor_pid"
vanishing_anchor_state='' vanishing_anchor_start='' vanishing_anchor_pgid='' vanishing_anchor_sid=''
test_process_identity "$vanishing_anchor_pid" vanishing_anchor_state vanishing_anchor_start \
vanishing_anchor_pgid vanishing_anchor_sid
[[ "$vanishing_anchor_state" != Z && "$vanishing_anchor_sid" == "$vanishing_anchor_pid" ]] \
|| fail 'vanishing anchor fixture was not a live session leader'
(
sleep 0.03
builtin kill -TERM -- "$vanishing_anchor_pid" 2>/dev/null || true
) & vanishing_anchor_killer=$!
if (
demo_command_matches() { return 2; }
demo_anchor_allows_signal "$ROOT" anvil "$vanishing_anchor_pid" "$vanishing_anchor_start" \
"$vanishing_anchor_pgid" validated
); then
vanishing_anchor_result=0
else
vanishing_anchor_result=$?
fi
wait "$vanishing_anchor_killer"
wait "$vanishing_anchor_pid" 2>/dev/null || true
((vanishing_anchor_result == 0)) || fail 'an exiting unverifiable anchor made shutdown fatal'
pass 'an unverifiable anchor is accepted only after it vanishes or becomes a zombie'
# Catches accepting a persistent live anchor merely because cmdline/exe cannot be read.
setsid /bin/sleep 120 & unreadable_anchor_pid=$!
track_process "$unreadable_anchor_pid"
unreadable_anchor_state='' unreadable_anchor_start='' unreadable_anchor_pgid='' unreadable_anchor_sid=''
test_process_identity "$unreadable_anchor_pid" unreadable_anchor_state unreadable_anchor_start \
unreadable_anchor_pgid unreadable_anchor_sid
[[ "$unreadable_anchor_state" != Z && "$unreadable_anchor_sid" == "$unreadable_anchor_pid" ]] \
|| fail 'unreadable anchor fixture was not a live session leader'
if (
demo_process_has_command_line() { return 1; }
demo_command_matches() { return 2; }
demo_anchor_allows_signal "$ROOT" anvil "$unreadable_anchor_pid" "$unreadable_anchor_start" \
"$unreadable_anchor_pgid" validated
); then
unreadable_anchor_result=0
else
unreadable_anchor_result=$?
fi
((unreadable_anchor_result != 0)) || fail 'a stable live anchor with unreadable command identity was accepted'
assert_alive "$unreadable_anchor_pid"
pass 'a stable live anchor remains refused when command identity is unverifiable'
# Catches treating an ESRCH-like signal race as fatal after the captured member has exited.
setsid /bin/bash -c 'trap "exit 0" TERM; while :; do sleep 0.01; done' & vanished_pid=$!
track_process "$vanished_pid"
@@ -251,7 +329,8 @@ kill() {
done
return 1
}
if demo_signal_member "$vanished_pid" "$vanished_start" "$vanished_pgid" "$vanished_sid" TERM; then
if demo_signal_member "$vanished_pid" "$vanished_start" "$vanished_pgid" "$vanished_sid" TERM \
2>"$TEST_ROOT/vanished-signal.err"; then
vanished_result=0
else
vanished_result=$?
@@ -259,6 +338,7 @@ fi
unset -f kill
wait "$vanished_pid" 2>/dev/null || true
((vanished_result == 0)) || fail 'a vanished member made its raced signal fatal'
[[ ! -s "$TEST_ROOT/vanished-signal.err" ]] || fail 'a benign vanished-member signal race emitted raw stderr'
pass 'a failed signal is idempotent after the captured member vanishes'
# Catches swallowing a real signal failure while the exact captured member remains live.
@@ -268,13 +348,17 @@ unsignaled_state='' unsignaled_start='' unsignaled_pgid='' unsignaled_sid=''
test_process_identity "$unsignaled_pid" unsignaled_state unsignaled_start unsignaled_pgid unsignaled_sid
[[ "$unsignaled_state" != Z ]] || fail 'unchanged signal-failure fixture exited early'
kill() { return 1; }
if demo_signal_member "$unsignaled_pid" "$unsignaled_start" "$unsignaled_pgid" "$unsignaled_sid" TERM; then
if demo_signal_member "$unsignaled_pid" "$unsignaled_start" "$unsignaled_pgid" "$unsignaled_sid" TERM \
2>"$TEST_ROOT/unsignaled-signal.err"; then
unsignaled_result=0
else
unsignaled_result=$?
fi
unset -f kill
((unsignaled_result != 0)) || fail 'a failed signal to the unchanged live member was accepted'
[[ $(<"$TEST_ROOT/unsignaled-signal.err") == \
"Failed to signal unchanged live PID $unsignaled_pid with TERM" ]] \
|| fail 'an unchanged-live signal failure lacked its controlled diagnostic'
assert_alive "$unsignaled_pid"
pass 'a failed signal remains fatal for the same live member tuple'
@@ -348,6 +432,80 @@ assert_dead "$child_pid"
assert_alive "$unrelated_pid"
pass 'bounded KILL removes a TERM-resistant owned child while an unrelated group survives'
# Catches a silent TERM-loop refusal after exact command identity definitively changes.
root=$(make_root term-refusal)
term_refusal_helper="$TEST_ROOT/bin/anvil"
term_refusal_child_file="$TEST_ROOT/term-refusal-child.pid"
cat >"$term_refusal_helper" <<'HELPER'
#!/usr/bin/env bash
sleep 120 &
printf '%s\n' "$!" >"$DEMO_CHILD_PID_FILE"
wait
sleep 120
HELPER
chmod +x "$term_refusal_helper"
DEMO_CHILD_PID_FILE="$term_refusal_child_file" setsid "$term_refusal_helper" "${ANVIL_EXACT_ARGS[@]}" \
& term_refusal_pid=$!
track_process "$term_refusal_pid"
for _ in {1..50}; do [[ -s "$term_refusal_child_file" ]] && break; sleep 0.02; done
[[ -s "$term_refusal_child_file" ]] || fail 'TERM-refusal helper child did not start'
term_refusal_start=$(start_tick "$term_refusal_pid")
if (
anchor_command_attempt=0
demo_command_matches() {
anchor_command_attempt=$((anchor_command_attempt + 1))
((anchor_command_attempt <= 2)) && return 0
return 1
}
demo_stop_launch "$root" anvil "$term_refusal_pid" "$term_refusal_start" "$term_refusal_pid"
) 2>"$TEST_ROOT/term-refusal.err"; then
term_refusal_result=0
else
term_refusal_result=$?
fi
((term_refusal_result != 0)) || fail 'a definitive TERM-loop anchor mismatch was accepted'
[[ $(<"$TEST_ROOT/term-refusal.err") == \
"Refusing TERM: anvil leader identity changed or remained unverifiable" ]] \
|| fail 'a TERM-loop anchor refusal lacked its controlled diagnostic'
assert_alive "$term_refusal_pid"
pass 'a definitive TERM-loop anchor mismatch is refused with a controlled diagnostic'
# Catches the child-exit/leader-exit race becoming fatal during transient command-read loss.
root=$(make_root anchor-race)
anchor_race_helper="$TEST_ROOT/bin/anvil"
anchor_race_child_file="$TEST_ROOT/anchor-race-child.pid"
cat >"$anchor_race_helper" <<'HELPER'
#!/usr/bin/env bash
sleep 120 &
printf '%s\n' "$!" >"$DEMO_CHILD_PID_FILE"
wait
sleep 0.05
HELPER
chmod +x "$anchor_race_helper"
DEMO_CHILD_PID_FILE="$anchor_race_child_file" setsid "$anchor_race_helper" "${ANVIL_EXACT_ARGS[@]}" \
& anchor_race_pid=$!
track_process "$anchor_race_pid"
for _ in {1..50}; do [[ -s "$anchor_race_child_file" ]] && break; sleep 0.02; done
[[ -s "$anchor_race_child_file" ]] || fail 'anchor-race helper child did not start'
anchor_race_start=$(start_tick "$anchor_race_pid")
if (
anchor_command_attempt=0
demo_command_matches() {
anchor_command_attempt=$((anchor_command_attempt + 1))
((anchor_command_attempt <= 2)) && return 0
return 2
}
demo_stop_launch "$root" anvil "$anchor_race_pid" "$anchor_race_start" "$anchor_race_pid"
); then
anchor_race_result=0
else
anchor_race_result=$?
fi
wait "$anchor_race_pid" 2>/dev/null || true
((anchor_race_result == 0)) || fail 'child exit made the concurrently exiting leader fatal'
assert_dead "$anchor_race_pid"
pass 'child-exit cleanup tolerates transient identity loss while the leader exits'
# Catches escalating after the recorded leader changes to a different command identity.
root=$(make_root mutated)
mutating_helper="$TEST_ROOT/bin/anvil"