Skip to content

Commit edc1d47

Browse files
committed
fix(ci): stop each HTTP end-to-end app's whole process session before the next one starts
1 parent 6583e33 commit edc1d47

2 files changed

Lines changed: 66 additions & 14 deletions

File tree

‎.github/scripts/stop-session.sh‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env bash
2+
# Stops every process in a session started with `setsid`, and returns only once none is left.
3+
#
4+
# Usage: stop-session.sh <session-leader-pid>
5+
#
6+
# `next dev` runs its server and workers as child processes. Waiting on `next dev` alone returns
7+
# while those can still be running, and the next app in the job reuses the same .next directory.
8+
# The leader stays a zombie until the shell that started it waits on it, so zombies don't count.
9+
set -u
10+
11+
leader=$1
12+
13+
running() {
14+
ps -s "$leader" -o pid=,stat= 2>/dev/null | awk '$2 !~ /^Z/ { found = 1 } END { exit !found }'
15+
}
16+
17+
leader_running() {
18+
ps -p "$leader" -o stat= 2>/dev/null | grep -qv '^Z'
19+
}
20+
21+
wait_for() {
22+
for _ in $(seq 1 100); do
23+
"$@" || return 0
24+
sleep 0.1
25+
done
26+
return 1
27+
}
28+
29+
kill -TERM -- "-$leader" 2>/dev/null || true
30+
wait_for leader_running
31+
if running; then
32+
echo "Processes from session $leader outlived its leader:"
33+
ps -s "$leader" -o pid,stat,etimes,args 2>/dev/null | cut -c1-200 || true
34+
fi
35+
wait_for running && exit 0
36+
37+
echo "::warning::Processes from session $leader were still running 10s after SIGTERM:"
38+
ps -s "$leader" -o pid,stat,etimes,args 2>/dev/null || true
39+
kill -KILL -- "-$leader" 2>/dev/null || true
40+
wait_for running && exit 0
41+
42+
echo "::error::Processes from session $leader survived SIGKILL."
43+
exit 1

‎.github/workflows/test-build.yml‎

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -233,14 +233,17 @@ jobs:
233233
server_log="$report_dir/scim-next.log"
234234
mkdir -p "$report_dir"
235235
rm -rf .next/dev
236-
node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 --port 3017 > "$server_log" 2>&1 &
236+
setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 --port 3017 > "$server_log" 2>&1 &
237237
server_pid=$!
238238
finish() {
239239
status=$?
240-
kill "$server_pid" 2>/dev/null || true
240+
bash ../../.github/scripts/stop-session.sh "$server_pid" || status=1
241241
wait "$server_pid" 2>/dev/null || true
242242
awk '/^ (GET|POST|PUT|PATCH|DELETE|HEAD) \/api\// { print }' "$server_log" > "$report_dir/scim-http-status.log"
243-
[ "$status" -eq 0 ] || tail -n 200 "$server_log"
243+
if [ "$status" -ne 0 ]; then
244+
tail -n 200 "$server_log"
245+
exit "$status"
246+
fi
244247
}
245248
trap finish EXIT
246249
fail_startup() {
@@ -270,11 +273,11 @@ jobs:
270273
# reservation, and the SCIM suite above asserts PostgreSQL rate-limit storage,
271274
# so workflow execution gets its own app rather than adding Redis to that one.
272275
#
273-
# Every app in this job starts from an empty Turbopack dev cache (.next/dev).
274-
# Restoring the one the previous app left, built with other NEXT_PUBLIC_*
275-
# values and cut off by `next dev` killing its server 100ms after SIGTERM,
276-
# panicked Turbopack ("inner_of_upper_lost_follower") or wedged the first
277-
# route compile in 12 of 175 CI runs.
276+
# The apps in this job share apps/sim/.next. Each starts from an empty Turbopack
277+
# dev cache: one written under other NEXT_PUBLIC_* values, by a server `next dev`
278+
# SIGKILLs 100ms after SIGTERM, can panic Turbopack or wedge a route compile on
279+
# restore. Each runs in its own session, and stop-session.sh returns only once
280+
# all of it has exited, so no app starts beside one still writing .next/dev.
278281
- name: Verify single-block workflow runs over real HTTP
279282
working-directory: apps/sim
280283
env:
@@ -292,14 +295,17 @@ jobs:
292295
server_log="$report_dir/stop-after-next.log"
293296
mkdir -p "$report_dir"
294297
rm -rf .next/dev
295-
node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 --port 3018 > "$server_log" 2>&1 &
298+
setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 --port 3018 > "$server_log" 2>&1 &
296299
server_pid=$!
297300
finish() {
298301
status=$?
299-
kill "$server_pid" 2>/dev/null || true
302+
bash ../../.github/scripts/stop-session.sh "$server_pid" || status=1
300303
wait "$server_pid" 2>/dev/null || true
301304
awk '/^ (GET|POST|PUT|PATCH|DELETE|HEAD) \/api\// { print }' "$server_log" > "$report_dir/stop-after-http-status.log"
302-
[ "$status" -eq 0 ] || tail -n 200 "$server_log"
305+
if [ "$status" -ne 0 ]; then
306+
tail -n 200 "$server_log"
307+
exit "$status"
308+
fi
303309
}
304310
trap finish EXIT
305311
fail_startup() {
@@ -340,14 +346,17 @@ jobs:
340346
server_log="$report_dir/desktop-inbox-next.log"
341347
mkdir -p "$report_dir"
342348
rm -rf .next/dev
343-
node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 --port 3019 > "$server_log" 2>&1 &
349+
setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 --port 3019 > "$server_log" 2>&1 &
344350
server_pid=$!
345351
finish() {
346352
status=$?
347-
kill "$server_pid" 2>/dev/null || true
353+
bash ../../.github/scripts/stop-session.sh "$server_pid" || status=1
348354
wait "$server_pid" 2>/dev/null || true
349355
awk '/^ (GET|POST|PUT|PATCH|DELETE|HEAD) \/api\// { print }' "$server_log" > "$report_dir/desktop-inbox-http-status.log"
350-
[ "$status" -eq 0 ] || tail -n 200 "$server_log"
356+
if [ "$status" -ne 0 ]; then
357+
tail -n 200 "$server_log"
358+
exit "$status"
359+
fi
351360
}
352361
trap finish EXIT
353362
fail_startup() {

0 commit comments

Comments
 (0)