#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: ./close_api_server.sh [--port PORT] [--pid PID] [--force] Defaults: PORT: 8001 Behavior: - If --pid is provided, stops that PID. - Otherwise, finds the listening PID(s) on --port and stops them. - By default, only stops processes whose cmdline contains "uvicorn" or "acestep.api_server". Use --force to skip this safety check. EOF } PORT="8001" PID="" FORCE="0" while [[ $# -gt 0 ]]; do case "$1" in --port) PORT="${2:-}"; shift 2 ;; --pid) PID="${2:-}"; shift 2 ;; --force) FORCE="1"; shift ;; -h|--help) usage; exit 0 ;; *) echo "Unknown argument: $1" >&2 usage exit 2 ;; esac done if [[ -n "$PORT" ]] && ! [[ "$PORT" =~ ^[0-9]+$ ]]; then echo "Invalid --port: $PORT" >&2 exit 2 fi if [[ -n "$PID" ]] && ! [[ "$PID" =~ ^[0-9]+$ ]]; then echo "Invalid --pid: $PID" >&2 exit 2 fi _cmdline() { local pid="$1" if [[ -r "/proc/${pid}/cmdline" ]]; then tr '\0' ' ' < "/proc/${pid}/cmdline" | sed 's/[[:space:]]\+/ /g' || true else echo "" fi } _is_target_process() { local pid="$1" local cmd cmd="$(_cmdline "$pid")" [[ "$cmd" == *"uvicorn"* || "$cmd" == *"acestep.api_server"* ]] } _find_pids_by_port() { local port="$1" local pids="" if command -v lsof >/dev/null 2>&1; then pids="$(lsof -nP -t -iTCP:"$port" -sTCP:LISTEN 2>/dev/null | tr '\n' ' ' || true)" elif command -v ss >/dev/null 2>&1; then # 输出示例:LISTEN 0 4096 127.0.0.1:8001 ... users:("python",pid=12345,fd=3) pids="$(ss -lptn "sport = :$port" 2>/dev/null | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u | tr '\n' ' ' || true)" elif command -v netstat >/dev/null 2>&1; then # 输出示例:tcp ... LISTEN 12345/python pids="$(netstat -lntp 2>/dev/null | awk -v p=":${port}" '$4 ~ p && $6=="LISTEN" {split($7,a,"/"); if (a[1] ~ /^[0-9]+$/) print a[1]}' | sort -u | tr '\n' ' ' || true)" elif command -v fuser >/dev/null 2>&1; then pids="$(fuser -n tcp "$port" 2>/dev/null | tr '\n' ' ' || true)" fi echo "$pids" } _stop_pid() { local pid="$1" if ! kill -0 "$pid" 2>/dev/null; then echo "PID $pid not running." return 0 fi if [[ "$FORCE" != "1" ]] && ! _is_target_process "$pid"; then echo "Skip PID $pid (cmdline does not look like uvicorn/acestep.api_server). Use --force to stop anyway." >&2 echo "cmdline: $(_cmdline "$pid")" >&2 return 3 fi echo "Stopping PID $pid..." kill -TERM "$pid" 2>/dev/null || true for _ in $(seq 1 30); do if ! kill -0 "$pid" 2>/dev/null; then echo "Stopped PID $pid." return 0 fi sleep 0.2 done echo "PID $pid did not exit; sending SIGKILL..." >&2 kill -KILL "$pid" 2>/dev/null || true sleep 0.1 if kill -0 "$pid" 2>/dev/null; then echo "Failed to kill PID $pid." >&2 return 1 fi echo "Killed PID $pid." return 0 } if [[ -n "$PID" ]]; then _stop_pid "$PID" exit $? fi pids="$(_find_pids_by_port "$PORT")" if [[ -z "${pids// }" ]]; then echo "No listening process found on port $PORT." exit 0 fi rc=0 for pid in $pids; do if [[ -n "$pid" ]]; then _stop_pid "$pid" || rc=$? fi done exit "$rc"