fix: close demo lifecycle races
This commit is contained in:
+19
-3
@@ -8,6 +8,7 @@ ANVIL_DEV_WORDS=(test test test test test test test test test test test junk)
|
||||
CLEANING=0
|
||||
ANVIL_PID='' ANVIL_START='' ANVIL_PGID=''
|
||||
VITE_PID='' VITE_START='' VITE_PGID=''
|
||||
ANVIL_VALIDATED=0 VITE_VALIDATED=0
|
||||
|
||||
cleanup() {
|
||||
local status=$? cleanup_status=0
|
||||
@@ -15,12 +16,14 @@ cleanup() {
|
||||
CLEANING=1
|
||||
trap - INT TERM EXIT
|
||||
if [[ -n "$VITE_PID" && -n "$VITE_START" && -n "$VITE_PGID" ]]; then
|
||||
demo_stop_launch "$ROOT" vite "$VITE_PID" "$VITE_START" "$VITE_PGID" || cleanup_status=1
|
||||
if ((VITE_VALIDATED == 1)); then demo_stop_launch "$ROOT" vite "$VITE_PID" "$VITE_START" "$VITE_PGID" || cleanup_status=1
|
||||
else demo_stop_raw_launch "$ROOT" vite "$VITE_PID" "$VITE_START" "$VITE_PGID" || cleanup_status=1; fi
|
||||
else
|
||||
demo_stop_recorded "$ROOT" vite || cleanup_status=1
|
||||
fi
|
||||
if [[ -n "$ANVIL_PID" && -n "$ANVIL_START" && -n "$ANVIL_PGID" ]]; then
|
||||
demo_stop_launch "$ROOT" anvil "$ANVIL_PID" "$ANVIL_START" "$ANVIL_PGID" || cleanup_status=1
|
||||
if ((ANVIL_VALIDATED == 1)); then demo_stop_launch "$ROOT" anvil "$ANVIL_PID" "$ANVIL_START" "$ANVIL_PGID" || cleanup_status=1
|
||||
else demo_stop_raw_launch "$ROOT" anvil "$ANVIL_PID" "$ANVIL_START" "$ANVIL_PGID" || cleanup_status=1; fi
|
||||
else
|
||||
demo_stop_recorded "$ROOT" anvil || cleanup_status=1
|
||||
fi
|
||||
@@ -34,10 +37,23 @@ trap cleanup EXIT
|
||||
capture_and_publish() {
|
||||
local kind=$1 pid=$2 start pgid
|
||||
for _ in {1..50}; do
|
||||
if demo_capture_process "$ROOT" "$kind" "$pid" start pgid; then
|
||||
if demo_capture_raw_launch "$pid" start pgid; then
|
||||
if [[ "$kind" == anvil ]]; then ANVIL_START=$start; ANVIL_PGID=$pgid
|
||||
else VITE_START=$start; VITE_PGID=$pgid; fi
|
||||
break
|
||||
fi
|
||||
demo_pid_is_running "$pid" || break
|
||||
sleep 0.02
|
||||
done
|
||||
[[ -n ${start:-} && -n ${pgid:-} ]] || {
|
||||
printf 'Could not capture raw ownership for launched %s PID %s; no signal will be guessed.\n' "$kind" "$pid" >&2
|
||||
return 1
|
||||
}
|
||||
for _ in {1..50}; do
|
||||
if demo_identity_matches "$ROOT" "$kind" "$pid" "$start" "$pgid"; then
|
||||
demo_publish_process "$ROOT" "$kind" "$pid" "$start" "$pgid"
|
||||
if [[ "$kind" == anvil ]]; then ANVIL_VALIDATED=1
|
||||
else VITE_VALIDATED=1; fi
|
||||
return
|
||||
fi
|
||||
demo_pid_is_running "$pid" || break
|
||||
|
||||
+49
-29
@@ -89,28 +89,25 @@ demo_interpreter_for_entry() {
|
||||
else demo_resolve_command "${interpreter[0]}"; fi
|
||||
}
|
||||
|
||||
demo_has_sequence_from() {
|
||||
demo_has_exact_tail() {
|
||||
local array_name=$1 begin=$2
|
||||
local -n argv_ref=$array_name
|
||||
shift 2
|
||||
local -a wanted=("$@")
|
||||
local i j
|
||||
for ((i = begin; i + ${#wanted[@]} <= ${#argv_ref[@]}; i++)); do
|
||||
for ((j = 0; j < ${#wanted[@]}; j++)); do
|
||||
[[ ${argv_ref[i+j]} == "${wanted[j]}" ]] || break
|
||||
done
|
||||
((j == ${#wanted[@]})) && return 0
|
||||
local index
|
||||
((${#argv_ref[@]} - begin == ${#wanted[@]})) || return 1
|
||||
for index in "${!wanted[@]}"; do
|
||||
[[ ${argv_ref[begin+index]} == "${wanted[index]}" ]] || return 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
demo_command_matches() {
|
||||
local root=$1 pid=$2 kind=$3 expected executable interpreter entry_index
|
||||
local -a arguments=()
|
||||
[[ -r "/proc/$pid/cmdline" && -e "/proc/$pid/exe" ]] || return 1
|
||||
mapfile -d '' -t arguments <"/proc/$pid/cmdline"
|
||||
{ mapfile -d '' -t arguments <"/proc/$pid/cmdline"; } 2>/dev/null || return 1
|
||||
((${#arguments[@]} > 0)) || return 1
|
||||
executable=$(readlink -f -- "/proc/$pid/exe") || return 1
|
||||
executable=$(readlink -f -- "/proc/$pid/exe" 2>/dev/null) || return 1
|
||||
|
||||
if [[ "$kind" == anvil ]]; then
|
||||
expected=$(demo_resolve_command anvil) || return 1
|
||||
@@ -128,13 +125,11 @@ demo_command_matches() {
|
||||
fi
|
||||
|
||||
if [[ "$kind" == anvil ]]; then
|
||||
demo_has_sequence_from arguments "$((entry_index + 1))" --host 127.0.0.1 || return 1
|
||||
demo_has_sequence_from arguments "$((entry_index + 1))" --port 8545 || return 1
|
||||
demo_has_sequence_from arguments "$((entry_index + 1))" --chain-id 31337 || return 1
|
||||
local dev_phrase='test test test test test test test test test test test junk'
|
||||
demo_has_exact_tail arguments "$((entry_index + 1))" \
|
||||
--host 127.0.0.1 --port 8545 --chain-id 31337 --mnemonic "$dev_phrase"
|
||||
else
|
||||
local -a expected_tail=(web --host 127.0.0.1 --port 5173)
|
||||
local -a actual_tail=("${arguments[@]:entry_index+1}")
|
||||
[[ "${actual_tail[*]}" == "${expected_tail[*]}" && ${#actual_tail[@]} -eq ${#expected_tail[@]} ]] || return 1
|
||||
demo_has_exact_tail arguments "$((entry_index + 1))" web --host 127.0.0.1 --port 5173
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -146,10 +141,17 @@ demo_identity_matches() {
|
||||
demo_command_matches "$root" "$pid" "$kind"
|
||||
}
|
||||
|
||||
demo_capture_raw_launch() {
|
||||
local pid=$1 start_name=$2 pgid_name=$3 state raw_actual_start raw_actual_pgid sid
|
||||
demo_read_process_identity "$pid" state raw_actual_start raw_actual_pgid sid || return 1
|
||||
[[ "$state" != Z && "$pid" == "$raw_actual_pgid" && "$pid" == "$sid" ]] || return 1
|
||||
printf -v "$start_name" '%s' "$raw_actual_start"
|
||||
printf -v "$pgid_name" '%s' "$raw_actual_pgid"
|
||||
}
|
||||
|
||||
demo_capture_process() {
|
||||
local root=$1 kind=$2 pid=$3 start_name=$4 pgid_name=$5 state actual_start actual_pgid sid
|
||||
demo_read_process_identity "$pid" state actual_start actual_pgid sid || return 1
|
||||
[[ "$state" != Z && "$pid" == "$actual_pgid" && "$pid" == "$sid" ]] || return 1
|
||||
local root=$1 kind=$2 pid=$3 start_name=$4 pgid_name=$5 actual_start actual_pgid
|
||||
demo_capture_raw_launch "$pid" actual_start actual_pgid || return 1
|
||||
demo_command_matches "$root" "$pid" "$kind" || return 1
|
||||
printf -v "$start_name" '%s' "$actual_start"
|
||||
printf -v "$pgid_name" '%s' "$actual_pgid"
|
||||
@@ -201,20 +203,29 @@ demo_collect_group_members() {
|
||||
}
|
||||
|
||||
demo_anchor_allows_signal() {
|
||||
local root=$1 kind=$2 leader=$3 expected_start=$4 expected_pgid=$5 state start pgid sid
|
||||
local root=$1 kind=$2 leader=$3 expected_start=$4 expected_pgid=$5 identity_mode=${6:-validated} state start pgid sid
|
||||
if ! demo_read_process_identity "$leader" state start pgid sid; then return 0; fi
|
||||
[[ "$start" == "$expected_start" && "$pgid" == "$expected_pgid" && "$sid" == "$expected_pgid" ]] || return 1
|
||||
[[ "$state" == Z ]] && return 0
|
||||
[[ "$identity_mode" == raw ]] && return 0
|
||||
demo_process_has_command_line "$leader" || return 0
|
||||
demo_command_matches "$root" "$leader" "$kind"
|
||||
if demo_command_matches "$root" "$leader" "$kind"; then return 0; fi
|
||||
if ! demo_read_process_identity "$leader" state start pgid sid; then return 0; fi
|
||||
[[ "$state" == Z ]] && return 0
|
||||
[[ "$start" == "$expected_start" && "$pgid" == "$expected_pgid" && "$sid" == "$expected_pgid" ]] || return 1
|
||||
return 1
|
||||
}
|
||||
|
||||
demo_signal_member() {
|
||||
local pid=$1 expected_start=$2 expected_pgid=$3 expected_sid=$4 signal=$5 state start pgid sid
|
||||
local pid=$1 expected_start=$2 expected_pgid=$3 expected_sid=$4 signal=$5 state start pgid sid signal_status
|
||||
if ! demo_read_process_identity "$pid" state start pgid sid; then return 0; fi
|
||||
[[ "$state" != Z ]] || return 0
|
||||
[[ "$start" == "$expected_start" && "$pgid" == "$expected_pgid" && "$sid" == "$expected_sid" ]] || return 2
|
||||
kill -"$signal" -- "$pid"
|
||||
if kill -"$signal" -- "$pid"; then return 0; else signal_status=$?; fi
|
||||
if ! demo_read_process_identity "$pid" state start pgid sid; then return 0; fi
|
||||
[[ "$state" != Z ]] || return 0
|
||||
[[ "$start" == "$expected_start" && "$pgid" == "$expected_pgid" && "$sid" == "$expected_sid" ]] || return 0
|
||||
return "$signal_status"
|
||||
}
|
||||
|
||||
demo_group_has_live_members() {
|
||||
@@ -224,12 +235,13 @@ demo_group_has_live_members() {
|
||||
((${#members[@]} > 0))
|
||||
}
|
||||
|
||||
demo_stop_launch() {
|
||||
local root=$1 kind=$2 leader=$3 expected_start=$4 expected_pgid=$5 member member_pid member_start result
|
||||
demo_stop_owned_session() {
|
||||
local root=$1 kind=$2 leader=$3 expected_start=$4 expected_pgid=$5 identity_mode=$6 member member_pid member_start result
|
||||
local -a members
|
||||
[[ "$identity_mode" == validated || "$identity_mode" == raw ]] || return 1
|
||||
demo_is_process_id "$leader" && demo_is_uint "$expected_start" && demo_is_process_id "$expected_pgid" \
|
||||
&& [[ "$leader" == "$expected_pgid" ]] || return 1
|
||||
demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid" || {
|
||||
demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid" "$identity_mode" || {
|
||||
printf 'Refusing %s PID %s: process identity changed\n' "$kind" "$leader" >&2
|
||||
return 1
|
||||
}
|
||||
@@ -243,13 +255,13 @@ demo_stop_launch() {
|
||||
for member in "${members[@]}"; do
|
||||
IFS=: read -r member_pid member_start <<<"$member"
|
||||
[[ "$member_pid" != "$leader" ]] || continue
|
||||
demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid" || return 1
|
||||
demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid" "$identity_mode" || return 1
|
||||
demo_signal_member "$member_pid" "$member_start" "$expected_pgid" "$expected_pgid" TERM || return 1
|
||||
done
|
||||
for member in "${members[@]}"; do
|
||||
IFS=: read -r member_pid member_start <<<"$member"
|
||||
[[ "$member_pid" == "$leader" ]] || continue
|
||||
demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid" || return 1
|
||||
demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid" "$identity_mode" || return 1
|
||||
demo_signal_member "$member_pid" "$member_start" "$expected_pgid" "$expected_pgid" TERM || return 1
|
||||
done
|
||||
for ((result = 0; result < ${DEMO_TERM_WAIT_ATTEMPTS:-50}; result++)); do
|
||||
@@ -264,7 +276,7 @@ demo_stop_launch() {
|
||||
((${#members[@]} > 0)) || break
|
||||
for member in "${members[@]}"; do
|
||||
IFS=: read -r member_pid member_start <<<"$member"
|
||||
if ! demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid"; then
|
||||
if ! demo_anchor_allows_signal "$root" "$kind" "$leader" "$expected_start" "$expected_pgid" "$identity_mode"; then
|
||||
printf 'Refusing KILL escalation: %s leader identity changed\n' "$kind" >&2
|
||||
return 1
|
||||
fi
|
||||
@@ -287,6 +299,14 @@ demo_stop_launch() {
|
||||
demo_remove_record "$root" "$kind"
|
||||
}
|
||||
|
||||
demo_stop_launch() {
|
||||
demo_stop_owned_session "$1" "$2" "$3" "$4" "$5" validated
|
||||
}
|
||||
|
||||
demo_stop_raw_launch() {
|
||||
demo_stop_owned_session "$1" "$2" "$3" "$4" "$5" raw
|
||||
}
|
||||
|
||||
demo_stop_recorded() {
|
||||
local root=$1 kind=$2 pid start pgid
|
||||
local pid_path="$root/.demo/$kind.pid" start_path="$root/.demo/$kind.start" pgid_path="$root/.demo/$kind.pgid"
|
||||
|
||||
@@ -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
|
||||
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=()
|
||||
PASSED=0
|
||||
STARTED_PID=
|
||||
@@ -15,7 +17,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 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 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 \
|
||||
@@ -29,7 +31,8 @@ cleanup() {
|
||||
"$TEST_ROOT/$root/web" "$TEST_ROOT/$root/deployments" "$TEST_ROOT/$root/tools" "$TEST_ROOT/$root/.demo" \
|
||||
"$TEST_ROOT/$root" 2>/dev/null || true
|
||||
done
|
||||
rm -f -- "$TEST_ROOT/bin/anvil" "$TEST_ROOT/bin/vite" "$TEST_ROOT/vite-child.pid"
|
||||
rm -f -- "$TEST_ROOT/bin/anvil" "$TEST_ROOT/bin/vite" "$TEST_ROOT/bin/leaderless" \
|
||||
"$TEST_ROOT/vite-child.pid" "$TEST_ROOT/leaderless-child.pid"
|
||||
rmdir -- "$TEST_ROOT/bin" "$TEST_ROOT" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
@@ -75,15 +78,17 @@ test_safe_stop() {
|
||||
local identity=$1 leader expected_start expected_pgid expected_sid state start pgid sid signal line member member_start
|
||||
local current_state current_start current_pgid current_sid
|
||||
IFS=: read -r leader expected_start expected_pgid expected_sid <<<"$identity"
|
||||
if test_process_identity "$leader" state start pgid sid && [[ "$start" != "$expected_start" ]]; then return 0; fi
|
||||
if test_process_identity "$leader" state start pgid sid \
|
||||
&& [[ "$start" != "$expected_start" || "$pgid" != "$expected_pgid" || "$sid" != "$expected_sid" ]]; then return 0; fi
|
||||
for signal in TERM KILL; do
|
||||
while read -r member pgid sid state; do
|
||||
[[ "$pgid" == "$expected_pgid" && "$sid" == "$expected_sid" && "$state" != Z ]] || continue
|
||||
test_process_identity "$member" state member_start pgid sid || continue
|
||||
[[ "$pgid" == "$expected_pgid" && "$sid" == "$expected_sid" ]] || continue
|
||||
test_process_identity "$leader" current_state current_start current_pgid current_sid || continue
|
||||
[[ "$current_start" == "$expected_start" && "$current_pgid" == "$expected_pgid" \
|
||||
&& "$current_sid" == "$expected_sid" ]] || continue
|
||||
if test_process_identity "$leader" current_state current_start current_pgid current_sid; then
|
||||
[[ "$current_start" == "$expected_start" && "$current_pgid" == "$expected_pgid" \
|
||||
&& "$current_sid" == "$expected_sid" ]] || continue
|
||||
fi
|
||||
test_process_identity "$member" current_state current_start current_pgid current_sid || continue
|
||||
[[ "$current_state" != Z && "$current_start" == "$member_start" \
|
||||
&& "$current_pgid" == "$expected_pgid" && "$current_sid" == "$expected_sid" ]] || continue
|
||||
@@ -138,7 +143,7 @@ wait
|
||||
HELPER
|
||||
chmod +x "$helper"
|
||||
if [[ "$kind" == anvil ]]; then
|
||||
setsid "$helper" --host 127.0.0.1 --port 8545 --chain-id 31337 >/dev/null 2>&1 &
|
||||
setsid "$helper" "${ANVIL_EXACT_ARGS[@]}" >/dev/null 2>&1 &
|
||||
else
|
||||
setsid "$helper" web --host 127.0.0.1 --port 5173 >/dev/null 2>&1 &
|
||||
fi
|
||||
@@ -152,7 +157,17 @@ HELPER
|
||||
STARTED_PID=$pid
|
||||
}
|
||||
|
||||
printf '1..16\n'
|
||||
assert_anvil_args_rejected() {
|
||||
local root=$1 label=$2 state start pgid sid pid
|
||||
shift 2
|
||||
setsid "$TEST_ROOT/bin/anvil" "$@" & pid=$!
|
||||
track_process "$pid"
|
||||
test_process_identity "$pid" state start pgid sid
|
||||
if demo_command_matches "$root" "$pid" anvil; then fail "$label Anvil arguments were accepted"; fi
|
||||
demo_stop_raw_launch "$root" anvil "$pid" "$start" "$pgid"
|
||||
}
|
||||
|
||||
printf '1..21\n'
|
||||
|
||||
# Catches cleanup treating a missing record as an error or signaling an inferred PID.
|
||||
root=$(make_root absent)
|
||||
@@ -205,6 +220,81 @@ if demo_stop_recorded "$root" vite 2>/dev/null; then fail 'argument-only Vite si
|
||||
assert_alive "$false_vite_pid"
|
||||
pass 'Vite identity requires the exact project-local entry and interpreter'
|
||||
|
||||
# Catches accepting extra or overriding Anvil arguments after a valid-looking prefix.
|
||||
root=$(make_root exact-anvil)
|
||||
mkdir -p "$TEST_ROOT/bin"
|
||||
cat >"$TEST_ROOT/bin/anvil" <<'HELPER'
|
||||
#!/usr/bin/env bash
|
||||
sleep 120 &
|
||||
wait
|
||||
HELPER
|
||||
chmod +x "$TEST_ROOT/bin/anvil"
|
||||
assert_anvil_args_rejected "$root" extra "${ANVIL_EXACT_ARGS[@]}" --silent
|
||||
assert_anvil_args_rejected "$root" duplicate "${ANVIL_EXACT_ARGS[@]}" --port 9999
|
||||
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 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"
|
||||
vanished_state='' vanished_start='' vanished_pgid='' vanished_sid=''
|
||||
test_process_identity "$vanished_pid" vanished_state vanished_start vanished_pgid vanished_sid
|
||||
[[ "$vanished_state" != Z ]] || fail 'vanishing race fixture exited before injection'
|
||||
kill() {
|
||||
builtin kill -TERM -- "$vanished_pid" 2>/dev/null || true
|
||||
for _ in {1..50}; do
|
||||
vanished_state_after=''
|
||||
if ! test_process_identity "$vanished_pid" vanished_state_after vanished_start_after vanished_pgid_after vanished_sid_after \
|
||||
|| [[ "$vanished_state_after" == Z ]]; then break; fi
|
||||
sleep 0.01
|
||||
done
|
||||
return 1
|
||||
}
|
||||
if demo_signal_member "$vanished_pid" "$vanished_start" "$vanished_pgid" "$vanished_sid" TERM; then
|
||||
vanished_result=0
|
||||
else
|
||||
vanished_result=$?
|
||||
fi
|
||||
unset -f kill
|
||||
wait "$vanished_pid" 2>/dev/null || true
|
||||
((vanished_result == 0)) || fail 'a vanished member made its raced signal fatal'
|
||||
pass 'a failed signal is idempotent after the captured member vanishes'
|
||||
|
||||
# Catches swallowing a real signal failure while the exact captured member remains live.
|
||||
setsid /bin/sleep 120 & unsignaled_pid=$!
|
||||
track_process "$unsignaled_pid"
|
||||
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
|
||||
unsignaled_result=0
|
||||
else
|
||||
unsignaled_result=$?
|
||||
fi
|
||||
unset -f kill
|
||||
((unsignaled_result != 0)) || fail 'a failed signal to the unchanged live member was accepted'
|
||||
assert_alive "$unsignaled_pid"
|
||||
pass 'a failed signal remains fatal for the same live member tuple'
|
||||
|
||||
# Catches command validation failure occurring before launch ownership is retained for cleanup.
|
||||
root=$(make_root raw-capture)
|
||||
setsid /bin/bash -c 'sleep 120 & wait' unexpected-entry --not-the-demo & raw_pid=$!
|
||||
track_process "$raw_pid"
|
||||
raw_start='' raw_pgid=''
|
||||
declare -F demo_capture_raw_launch >/dev/null || fail 'missing raw launch identity capture'
|
||||
declare -F demo_stop_raw_launch >/dev/null || fail 'missing command-independent raw launch cleanup'
|
||||
for _ in {1..50}; do
|
||||
if demo_capture_raw_launch "$raw_pid" raw_start raw_pgid; then break; fi
|
||||
sleep 0.02
|
||||
done
|
||||
[[ -n ${raw_start:-} && "$raw_pgid" == "$raw_pid" ]] || fail 'raw launch identity was not captured'
|
||||
if demo_command_matches "$root" "$raw_pid" anvil; then fail 'raw test command unexpectedly passed Anvil validation'; fi
|
||||
demo_stop_raw_launch "$root" anvil "$raw_pid" "$raw_start" "$raw_pgid"
|
||||
assert_dead "$raw_pid"
|
||||
pass 'raw launch ownership safely cleans a command-validation failure'
|
||||
|
||||
# Catches recycled PID ownership being inferred from PID and command alone.
|
||||
root=$(make_root reused)
|
||||
start_owned "$root" anvil
|
||||
@@ -267,7 +357,7 @@ trap 'exec -a changed-after-term /bin/sleep 120' TERM
|
||||
while :; do sleep 0.05; done
|
||||
HELPER
|
||||
chmod +x "$mutating_helper"
|
||||
setsid "$mutating_helper" --host 127.0.0.1 --port 8545 --chain-id 31337 & mutated_pid=$!
|
||||
setsid "$mutating_helper" "${ANVIL_EXACT_ARGS[@]}" & mutated_pid=$!
|
||||
track_process "$mutated_pid"
|
||||
write_record "$root" anvil "$mutated_pid" "$(start_tick "$mutated_pid")" "$mutated_pid"
|
||||
if demo_stop_recorded "$root" anvil 2>/dev/null; then fail 'changed identity was escalated instead of rejected'; fi
|
||||
@@ -288,6 +378,36 @@ assert_absent "$root/.demo/anvil.start"
|
||||
assert_absent "$root/.demo/anvil.pgid"
|
||||
pass 'in-memory launch identity safely cleans a partial unpublished record'
|
||||
|
||||
# Catches test cleanup leaking a TERM-resistant session member after its original leader is reaped.
|
||||
leaderless_helper="$TEST_ROOT/bin/leaderless"
|
||||
leaderless_child_file="$TEST_ROOT/leaderless-child.pid"
|
||||
cat >"$leaderless_helper" <<'HELPER'
|
||||
#!/usr/bin/env bash
|
||||
bash -c 'trap "" TERM; exec sleep 120' &
|
||||
printf '%s\n' "$!" >"$DEMO_CHILD_PID_FILE"
|
||||
sleep 1
|
||||
HELPER
|
||||
chmod +x "$leaderless_helper"
|
||||
DEMO_CHILD_PID_FILE="$leaderless_child_file" setsid "$leaderless_helper" & leaderless_pid=$!
|
||||
track_process "$leaderless_pid"
|
||||
leaderless_identity=${TEST_IDENTITIES[${#TEST_IDENTITIES[@]}-1]}
|
||||
for _ in {1..50}; do [[ -s "$leaderless_child_file" ]] && break; sleep 0.02; done
|
||||
[[ -s "$leaderless_child_file" ]] || fail 'leaderless cleanup child did not start'
|
||||
leaderless_child=$(<"$leaderless_child_file")
|
||||
child_state='' child_start='' child_pgid='' child_sid=''
|
||||
test_process_identity "$leaderless_child" child_state child_start child_pgid child_sid
|
||||
[[ "$child_state" != Z ]] || fail 'leaderless cleanup child exited early'
|
||||
wait "$leaderless_pid"
|
||||
test_safe_stop "$leaderless_identity"
|
||||
if test_process_identity "$leaderless_child" current_state current_start current_pgid current_sid \
|
||||
&& [[ "$current_state" != Z ]]; then
|
||||
if [[ "$current_start" == "$child_start" && "$current_pgid" == "$child_pgid" && "$current_sid" == "$child_sid" ]]; then
|
||||
builtin kill -KILL -- "$leaderless_child" 2>/dev/null || true
|
||||
fi
|
||||
fail 'test cleanup leaked a session member after the leader was reaped'
|
||||
fi
|
||||
pass 'test cleanup safely stops surviving members after leader reaping'
|
||||
|
||||
# Catches reset deleting arbitrary neighbors or leaving known reproducible local artifacts.
|
||||
root=$(make_root local-reset)
|
||||
for kind in anvil vite; do printf 'log\n' >"$root/.demo/$kind.log"; printf '1\n' >"$root/.demo/$kind.start"; printf '1\n' >"$root/.demo/$kind.pgid"; done
|
||||
|
||||
Reference in New Issue
Block a user