fix: close demo lifecycle races

This commit is contained in:
golem
2026-08-21 04:57:14 -06:00
parent a710106932
commit a28ad76868
5 changed files with 295 additions and 47 deletions
+49 -29
View File
@@ -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"