fix(run.sh): correct PID capture with process substitution, add SIGTERM trap, 3-phase cleanup

Problem: pipeline cmd | awk & captured awks PID instead of Python PID.
When cleanup killed AGENT_PID, awk died but Gradio kept port 7860 occupied.

Fix:
- Replace | awk & with > >(awk) 2>&1 & (process substitution) so 0 gives real PID
- Trap SIGTERM + SIGHUP in addition to SIGINT
- Three-phase cleanup: SIGTERM -> wait 1s -> SIGKILL -> fuser port fallback
This commit is contained in:
2026-06-29 17:43:42 +03:00
parent 12678c637b
commit 7090ec979f

48
run.sh
View File

@@ -186,21 +186,43 @@ setup_frontend
cleanup() {
echo ""
echo "Stopping services..."
# Phase 1: kill by tracked PID (process substitution ensures correct PID capture)
if [ -n "$BACKEND_PID" ]; then
kill $BACKEND_PID 2>/dev/null || true
kill -TERM $BACKEND_PID 2>/dev/null || true
fi
if [ -n "$FRONTEND_PID" ]; then
kill $FRONTEND_PID 2>/dev/null || true
kill -TERM $FRONTEND_PID 2>/dev/null || true
fi
if [ -n "$AGENT_PID" ]; then
kill $AGENT_PID 2>/dev/null || true
kill -TERM $AGENT_PID 2>/dev/null || true
fi
# Phase 2: wait for graceful exit (up to 2s), then force kill
sleep 1
if [ -n "$BACKEND_PID" ]; then
kill -KILL $BACKEND_PID 2>/dev/null || true
fi
if [ -n "$FRONTEND_PID" ]; then
kill -KILL $FRONTEND_PID 2>/dev/null || true
fi
if [ -n "$AGENT_PID" ]; then
kill -KILL $AGENT_PID 2>/dev/null || true
fi
# Phase 3: fuser fallback — kill anything still on our ports (belt-and-suspenders)
if command -v fuser &>/dev/null; then
fuser -k ${AGENT_PORT}/tcp 2>/dev/null || true
fuser -k ${BACKEND_PORT}/tcp 2>/dev/null || true
fuser -k ${FRONTEND_PORT}/tcp 2>/dev/null || true
fi
echo "Services stopped."
exit 0
}
# Trap SIGINT (Ctrl+C)
trap cleanup SIGINT
# Trap SIGINT (Ctrl+C), SIGTERM (kill), and SIGHUP (terminal close)
trap cleanup SIGINT SIGTERM SIGHUP
# Start Backend
start_backend() {
@@ -234,8 +256,8 @@ start_backend() {
fi
local _color=$'\033[0;34m[Backend]\033[0m '
PYTHONUNBUFFERED=1 python3 -m uvicorn src.app:app --port "$BACKEND_PORT" \
"${uvicorn_env_args[@]}" "${reload_args[@]}" 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
"${uvicorn_env_args[@]}" "${reload_args[@]}" \
> >(awk -v prefix="$_color " '{print prefix $0; fflush()}') 2>&1 &
BACKEND_PID=$!
cd ..
}
@@ -247,8 +269,8 @@ start_frontend() {
local _color=$'\033[0;32m[Frontend]\033[0m '
BACKEND_URL="http://127.0.0.1:${BACKEND_PORT}" \
GRADIO_URL="http://127.0.0.1:${AGENT_PORT}" \
npm run dev -- --port "$FRONTEND_PORT" 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
npm run dev -- --port "$FRONTEND_PORT" \
> >(awk -v prefix="$_color " '{print prefix $0; fflush()}') 2>&1 &
FRONTEND_PID=$!
cd ..
}
@@ -276,11 +298,11 @@ start_agent() {
if [ "$DEV_MODE" = "true" ]; then
echo -e "\033[0;35m[Agent]\033[0m Hot-reload enabled via watchfiles (watching src/agent/)"
PYTHONUNBUFFERED=1 python3 -m watchfiles --filter python \
"python -m src.agent.run" src/agent 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
"python -m src.agent.run" src/agent \
> >(awk -v prefix="$_color " '{print prefix $0; fflush()}') 2>&1 &
else
PYTHONUNBUFFERED=1 python3 -m src.agent.run 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
PYTHONUNBUFFERED=1 python3 -m src.agent.run \
> >(awk -v prefix="$_color " '{print prefix $0; fflush()}') 2>&1 &
fi
AGENT_PID=$!
cd ..