11#! /usr/bin/env bash
2- # Stops every process in a session started with `setsid` , and returns only once none is left.
2+ # Stops every process an app started, and returns only once none is left.
33#
4- # Usage: stop-session.sh <session-leader-pid>
4+ # Usage: stop-session.sh <session-leader-pid> <app-tag>
55#
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- # Every member of the session is signalled, including one that moved to its own process group,
9- # and one that started after the first signal.
6+ # Start the app as `E2E_APP=<app-tag> setsid <command> &`. Its processes are the members of the
7+ # leader's session, plus any process that inherited `E2E_APP=<app-tag>` but left the session.
8+ # `next dev` needs both: its server and workers stay in the session, while its telemetry flush is
9+ # spawned detached, in a session of its own, and writes .next/dev/trace after `next dev` exits.
10+ # The next app in the job wipes that directory, so it must not start while any of them runs.
11+ #
12+ # Every member is signalled, including one that moved to its own process group, and one that
13+ # started after the first signal.
1014#
1115# Zombies don't count as running: they have exited and only await reaping. The leader is one
1216# until the shell that started it waits on it, which that shell does after this returns.
1317set -u
1418
1519leader=$1
20+ tag=$2
1621grace_seconds=10
1722
18- session_pids () {
19- ps -s " $leader " -o pid=,stat= 2> /dev/null | awk ' $2 !~ /^Z/ { print $1 }'
23+ app_pids () {
24+ {
25+ ps -s " $leader " -o pid=,stat= 2> /dev/null | awk ' $2 !~ /^Z/ { print $1 }'
26+ grep -lzx " E2E_APP=$tag " /proc/[0-9]* /environ 2> /dev/null | cut -d/ -f3
27+ } | sort -u
2028}
2129
22- signal_session () {
30+ signal_app () {
2331 local pids
24- pids=$( session_pids )
32+ pids=$( app_pids )
2533 [ -z " $pids " ] || kill " -$1 " $pids 2> /dev/null || true
2634}
2735
2836running () {
29- [ -n " $( session_pids ) " ]
37+ [ -n " $( app_pids ) " ]
3038}
3139
3240leader_running () {
3341 ps -p " $leader " -o stat= 2> /dev/null | grep -qv ' ^Z'
3442}
3543
44+ list_app () {
45+ local pids
46+ pids=$( app_pids | paste -sd, -)
47+ [ -z " $pids " ] || ps -p " $pids " -o pid,sid,stat,etimes,args 2> /dev/null | cut -c1-200 || true
48+ }
49+
3650# Centiseconds since boot: monotonic, and independent of the locale's decimal separator.
3751now_cs () {
3852 local uptime
@@ -47,7 +61,7 @@ signal_while() {
4761 shift
4862 while (( 10 #$(now_cs) < deadline)) ; do
4963 " $@ " || return 0
50- signal_session " $signal "
64+ signal_app " $signal "
5165 sleep 0.1
5266 done
5367 ! " $@ "
@@ -56,15 +70,15 @@ signal_while() {
5670deadline=$(( 10 #$(now_cs) + grace_seconds * 100 ))
5771signal_while TERM leader_running
5872if ! leader_running && running; then
59- echo " Processes from session $leader outlived its leader:"
60- ps -s " $leader " -o pid,stat,etimes,args 2> /dev/null | cut -c1-200 || true
73+ echo " Processes from app $tag outlived its leader:"
74+ list_app
6175fi
6276signal_while TERM running && exit 0
6377
64- echo " ::warning::Processes from session $leader were still running ${grace_seconds} s after SIGTERM:"
65- ps -s " $leader " -o pid,stat,etimes,args 2> /dev/null | cut -c1-200 || true
78+ echo " ::warning::Processes from app $tag were still running ${grace_seconds} s after SIGTERM:"
79+ list_app
6680deadline=$(( 10 #$(now_cs) + grace_seconds * 100 ))
6781signal_while KILL running && exit 0
6882
69- echo " ::error::Processes from session $leader survived SIGKILL for ${grace_seconds} s."
83+ echo " ::error::Processes from app $tag survived SIGKILL for ${grace_seconds} s."
7084exit 1
0 commit comments