thread-master/old/scripts/dev-all-stop.sh

123 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -uo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
RUN_DIR="$ROOT_DIR/.run"
PID_FILE="$RUN_DIR/dev-all.pids"
# Processes actually spawned by dev-all.sh look like this on the command
# line (dev-all.sh always `cd`s into the target dir first, so the binary is
# invoked with a *relative* path — never anchor these on an absolute path).
# Env-var assignments (e.g. HAIXUN_NODE_WORKER_TYPE=...) must never be used
# as a match pattern: once `env FOO=bar cmd` execs `cmd`, the assignment is
# gone from the process's argv/cmdline and can never match again.
PATTERNS=(
"go workers|bin/worker -f etc/gateway.worker.yaml"
"gateway|bin/gateway -f etc/gateway.yaml"
"node style-8d worker|style-8d-worker.ts"
"keyword-search tool|keyword-search-server.ts"
"resolve-media tool|resolve-media-server.ts"
"web dev server|vite.*--strictPort"
)
port_in_use() {
local port="$1"
ss -tlnp 2>/dev/null | grep -qE ":${port} "
}
free_port() {
local port="$1"
if ! port_in_use "$port"; then
return 0
fi
echo "[dev-all-stop] freeing port :$port"
if command -v fuser >/dev/null 2>&1; then
fuser -k "${port}/tcp" 2>/dev/null || true
else
local line pid
while IFS= read -r line; do
for pid in $(grep -oE 'pid=[0-9]+' <<<"$line" | cut -d= -f2); do
kill "$pid" 2>/dev/null || true
done
done < <(ss -tlnp 2>/dev/null | grep -E ":${port} " || true)
fi
sleep 0.3
}
# Stop everything dev-all.sh recorded in its pidfile. Each recorded pid is a
# process-group leader (dev-all.sh launches jobs via `setsid`), so signalling
# the negative pid takes down the whole subtree (npm -> tsx -> node, etc.),
# not just the immediate child. This is the reliable path and also covers
# runs where dev-all.sh itself was killed abruptly (e.g. `kill -9`, closed
# terminal) and never got to run its own cleanup trap.
stop_from_pidfile() {
if [[ ! -f "$PID_FILE" ]]; then
return 0
fi
echo "[dev-all-stop] stopping processes recorded in $PID_FILE"
local pid label
while read -r pid label; do
[[ -n "${pid:-}" ]] || continue
if kill -0 "$pid" 2>/dev/null; then
echo "[dev-all-stop] -> ${label:-pgid $pid} (pgid $pid): TERM"
kill -TERM -- "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
fi
done < "$PID_FILE"
sleep 1
while read -r pid label; do
[[ -n "${pid:-}" ]] || continue
if kill -0 "$pid" 2>/dev/null; then
echo "[dev-all-stop] -> ${label:-pgid $pid} (pgid $pid) 仍在執行,改用 KILL"
kill -KILL -- "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
fi
done < "$PID_FILE"
rm -f "$PID_FILE"
}
# Fallback for anything the pidfile doesn't know about: processes started
# before this pidfile mechanism existed, started outside dev-all.sh, or left
# over from a crashed run on a previous checkout. Matches the *actual*
# observed command line, not env vars or absolute paths (see PATTERNS above).
stop_pattern() {
local label="$1"
local pattern="$2"
if pgrep -f "$pattern" >/dev/null 2>&1; then
echo "[dev-all-stop] stopping leftover: $label"
pkill -f "$pattern" 2>/dev/null || true
sleep 0.3
pkill -9 -f "$pattern" 2>/dev/null || true
fi
}
stop_from_pidfile
for port in 8890 9101 9102; do
free_port "$port"
done
for port in $(seq 5173 5199); do
free_port "$port"
done
for entry in "${PATTERNS[@]}"; do
stop_pattern "${entry%%|*}" "${entry#*|}"
done
sleep 0.5
# Verify instead of just hoping: print anything that survived so it's
# obvious when a leftover truly needs manual attention.
all_patterns="$(printf '%s|' "${PATTERNS[@]#*|}")"
all_patterns="${all_patterns%|}"
remaining="$(pgrep -f "$all_patterns" 2>/dev/null || true)"
if [[ -n "$remaining" ]]; then
echo "[dev-all-stop] 警告:仍有殘留程序未關閉:" >&2
# shellcheck disable=SC2086
ps -o pid,ppid,cmd -p $(echo "$remaining" | paste -sd, -) 2>/dev/null >&2 || true
echo "[dev-all-stop] 可手動執行: kill -9 $(echo "$remaining" | paste -sd' ' -)" >&2
else
echo "[dev-all-stop] 已確認沒有殘留的 dev-all 相關程序"
fi
echo "[dev-all-stop] done"