diff --git a/.gitignore b/.gitignore index f6fcba1..20bd53e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ spike-webswing/fontconfig.properties # behind the numbers in README "Findings". results/watch/ results/*.log +results/*.pid diff --git a/README.md b/README.md index fecccea..48f2d23 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ Two people can run side by side by picking different ports and an instance name, which namespaces the generated configs and logs: INSTANCE=claude PORT_WS=8090 PORT_MM=8860 ./spike-webswing/start.sh + ./spike-webswing/stop.sh claude + +**Stop instances with `stop.sh`, never `pkill -f spike-webswing/start.sh`** — that +pattern matches every instance on the box and will kill someone else's live game. +`start.sh` publishes `results/.pid` with the process group ids of its +children, and `stop.sh` signals exactly those. Still shared between instances: MegaMek's `mmconf/` (both seed the same `clientsettings.xml`, so identical content) and `logs/`. diff --git a/spike-webswing/start.sh b/spike-webswing/start.sh index f232508..50e96db 100755 --- a/spike-webswing/start.sh +++ b/spike-webswing/start.sh @@ -29,6 +29,7 @@ INSTANCE="${INSTANCE:-default}" WS_CONFIG="$WS/webswing-$INSTANCE.config" WS_JETTY="$WS/jetty-$INSTANCE.properties" WS_LOG="logs/ws-$INSTANCE.log" +PID_FILE="$ROOT/results/$INSTANCE.pid" RAW_LOG="$ROOT/results/host-$INSTANCE-raw.log" [ -x "$JAVA" ] || { echo "ERROR: JDK missing; run scripts/fetch-deps.sh" >&2; exit 1; } @@ -93,6 +94,7 @@ cleanup() { [ -n "$TAIL_PID" ] && kill "$TAIL_PID" 2>/dev/null || true kill_group "$HOST_PID" kill_group "$WS_PID" # its group includes any MegaMek client it spawned + rm -f "$PID_FILE" echo "done." } trap cleanup INT TERM EXIT @@ -159,5 +161,16 @@ grep --line-buffered -vE "^(ERROR|WARN|INFO) " < <(tail -f -n +1 "$RAW_LOG") \ | tee "$ROOT/results/host-$INSTANCE.log" & TAIL_PID=$! +# Both children were started with setsid, so their PID is also their process +# group id and can be signalled as a group. +cat > "$PID_FILE" <.pid holding the process group ids of its two children, so we +# can signal exactly those. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +INSTANCE="${1:-${INSTANCE:-default}}" +PID_FILE="$ROOT/results/$INSTANCE.pid" + +if [ ! -f "$PID_FILE" ]; then + echo "No PID file for instance '$INSTANCE' ($PID_FILE)." + echo "Running instances:" + shopt -s nullglob + found=0 + for f in "$ROOT"/results/*.pid; do + echo " $(basename "${f%.pid}")" + found=1 + done + [ "$found" = "0" ] && echo " (none)" + exit 1 +fi + +# shellcheck disable=SC1090 +. "$PID_FILE" + +kill_group() { + local pid="${1:-}" what="$2" + [ -n "$pid" ] || return 0 + kill -0 "$pid" 2>/dev/null || return 0 + echo " stopping $what (pgid $pid)" + kill -TERM "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true + for _ in $(seq 20); do kill -0 "$pid" 2>/dev/null || return 0; sleep 0.25; done + kill -KILL "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true +} + +echo "stopping instance '$INSTANCE' (web :${PORT_WS:-?}, game :${PORT_MM:-?})" + +# The script's own EXIT trap would normally do this, but stop it first so it does +# not race us, then clean up the children directly in case it was already gone. +if [ -n "${SCRIPT_PID:-}" ] && kill -0 "$SCRIPT_PID" 2>/dev/null; then + kill -TERM "$SCRIPT_PID" 2>/dev/null || true + for _ in $(seq 20); do kill -0 "$SCRIPT_PID" 2>/dev/null || break; sleep 0.25; done +fi +kill_group "${HOST_PGID:-}" "MegaMek host" +kill_group "${WEBSWING_PGID:-}" "Webswing" + +rm -f "$PID_FILE" +echo "done."