Repository navigation
Expand file tree
/
Copy pathrun
More file actions
executable file
·150 lines (133 loc) · 3.89 KB
/
Copy pathrun
File metadata and controls
executable file
·150 lines (133 loc) · 3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/bin/bash
# Two-party local test harness. Pick a fresh random port per invocation so back-
# to-back ctest runs don't collide on a socket still in TIME_WAIT. Port travels
# via EMP_PORT (read by peer_port()); only the party id is a positional arg.
export EMP_PORT=$(( (RANDOM % 30000) + 20000 ))
if [[ $# -lt 1 ]]; then
echo "usage: $0 <binary> [args]" >&2
exit 2
fi
timeout_seconds=${EMP_RUN_TIMEOUT:-120}
case "$timeout_seconds" in
''|*[!0-9]*)
echo "run: EMP_RUN_TIMEOUT must be a positive integer" >&2
exit 2
;;
esac
if (( timeout_seconds <= 0 )); then
echo "run: EMP_RUN_TIMEOUT must be a positive integer" >&2
exit 2
fi
program=$1
shift
alice_pid=
bob_pid=
alice_done=0
bob_done=0
# Stop a party's entire process group, escalating if any descendant ignores
# TERM. Each party is launched as its group's leader below, so a timed-out
# wrapper cannot orphan a grandchild that keeps a pipe or socket open.
terminate_group() {
local pid=$1
local attempts=0
if [[ -n "$pid" ]] && kill -0 -- "-$pid" 2>/dev/null; then
kill -TERM -- "-$pid" 2>/dev/null || true
while kill -0 -- "-$pid" 2>/dev/null && (( attempts < 10 )); do
sleep 0.02
((attempts += 1))
done
if kill -0 -- "-$pid" 2>/dev/null; then
kill -KILL -- "-$pid" 2>/dev/null || true
fi
fi
}
# Stop a party and all of its descendants, then reap the direct child. No
# Bash 4+ wait -n support is needed (macOS still ships Bash 3.2).
terminate_and_reap() {
local pid=$1
terminate_group "$pid"
wait "$pid" 2>/dev/null || true
}
# Any exit path not reached after both waits (signal, timeout, or unexpected
# shell failure) must not leave a peer blocked in accept()/recv(). Preserve the
# status that triggered EXIT rather than replacing it with a cleanup status.
cleanup() {
local status=$?
trap - EXIT INT TERM HUP
if (( !alice_done )); then
terminate_and_reap "$alice_pid"
fi
if (( !bob_done )); then
terminate_and_reap "$bob_pid"
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
# Monitor mode gives each background job its own process group on both Linux
# and macOS Bash. Disable it immediately after launch; the group membership
# remains while the rest of the supervisor retains ordinary script semantics.
# ALICE listens and BOB's connector retries until the listener is ready, so a
# fixed startup sleep is unnecessary. Quote the program and argument vector so
# spaces and wildcard characters reach both parties unchanged.
set -m
"$program" 1 "$@" &
alice_pid=$!
"$program" 2 "$@" &
bob_pid=$!
set +m
deadline=$((SECONDS + timeout_seconds))
alice_status=0
bob_status=0
# Bash records a completed background child's status until wait consumes it.
# Polling kill -0 lets this Bash-3.2-compatible loop notice whichever party
# finishes first, then wait retrieves its real exit status.
while (( !alice_done || !bob_done )); do
if (( !alice_done )) && ! kill -0 "$alice_pid" 2>/dev/null; then
wait "$alice_pid"
alice_status=$?
terminate_group "$alice_pid"
alice_done=1
if (( alice_status != 0 )); then
echo "run: party 1 failed with status $alice_status" >&2
if (( !bob_done )); then
terminate_and_reap "$bob_pid"
bob_done=1
fi
exit "$alice_status"
fi
fi
if (( !bob_done )) && ! kill -0 "$bob_pid" 2>/dev/null; then
wait "$bob_pid"
bob_status=$?
terminate_group "$bob_pid"
bob_done=1
if (( bob_status != 0 )); then
echo "run: party 2 failed with status $bob_status" >&2
if (( !alice_done )); then
terminate_and_reap "$alice_pid"
alice_done=1
fi
exit "$bob_status"
fi
fi
if (( alice_done && bob_done )); then
break
fi
if (( SECONDS >= deadline )); then
echo "run: parties timed out after ${timeout_seconds}s (EMP_PORT=$EMP_PORT)" >&2
if (( !alice_done )); then
terminate_and_reap "$alice_pid"
alice_done=1
fi
if (( !bob_done )); then
terminate_and_reap "$bob_pid"
bob_done=1
fi
exit 124
fi
sleep 0.01
done
exit 0