File size: 3,109 Bytes
0228d48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/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"