-
Notifications
You must be signed in to change notification settings - Fork 302
Labels
bugSomething isn't workingSomething isn't workingtriageIssues / Features awaiting triageIssues / Features awaiting triage
Milestone
Description
[Bug]: make serve doesn't check for already running instance
π Bug Description
Priority: Medium (Operations/Developer Experience)
Description:
Running make serve
in multiple terminals allows multiple instances of the gateway to start, potentially causing port conflicts, resource contention, and confusing behavior. The command should detect if an instance is already running and either fail gracefully or offer to stop the existing instance.
π Current Behavior
# Terminal 1
$ make serve
Starting MCP Gateway on http://0.0.0.0:4444...
# Terminal 2 (same directory)
$ make serve
Starting MCP Gateway on http://0.0.0.0:4444... # Starts another instance!
Both instances attempt to run, leading to:
- Port binding conflicts
- Database connection pool exhaustion
- Unpredictable request routing
- Resource waste
- Confusion about which instance is actually serving
β Steps to Reproduce
- Open terminal 1:
make serve
- Wait for service to start
- Open terminal 2:
make serve
- Observe that both attempt to run without any warning
β Expected Behavior
# Terminal 2
$ make serve
β MCP Gateway already running (PID: 12345, Port: 4444)
π‘ Options:
β’ Stop it first: make serve-stop
β’ Or use: make serve-restart
π¬ Investigation
The issue stems from:
- No PID file management in
run-gunicorn.sh
- No port availability check before starting
- No process detection for existing instances
π οΈ Proposed Solution
1. Update Makefile with PID management:
# PID file location
PID_FILE := /tmp/mcpgateway.pid
SERVE_PORT := 4444
serve: serve-check
@echo "π Starting MCP Gateway on port $(SERVE_PORT)..."
@./run-gunicorn.sh & echo $$! > $(PID_FILE)
@echo "β
Started with PID: $$(cat $(PID_FILE))"
serve-check:
@# Check if PID file exists and process is running
@if [ -f $(PID_FILE) ] && kill -0 $$(cat $(PID_FILE)) 2>/dev/null; then \
echo "β MCP Gateway already running (PID: $$(cat $(PID_FILE)))"; \
echo "π‘ Options:"; \
echo " β’ Stop it first: make serve-stop"; \
echo " β’ Or use: make serve-restart"; \
exit 1; \
fi
@# Check if port is in use
@if lsof -Pi :$(SERVE_PORT) -sTCP:LISTEN -t >/dev/null 2>&1; then \
echo "β Port $(SERVE_PORT) is already in use"; \
echo "π‘ Another process is using this port. Check with:"; \
echo " lsof -i :$(SERVE_PORT)"; \
exit 1; \
fi
@# Clean up stale PID file
@rm -f $(PID_FILE)
serve-stop:
@if [ -f $(PID_FILE) ]; then \
if kill -0 $$(cat $(PID_FILE)) 2>/dev/null; then \
echo "π Stopping MCP Gateway (PID: $$(cat $(PID_FILE)))..."; \
kill $$(cat $(PID_FILE)); \
sleep 2; \
if kill -0 $$(cat $(PID_FILE)) 2>/dev/null; then \
echo "β οΈ Process still running, force killing..."; \
kill -9 $$(cat $(PID_FILE)); \
fi; \
rm -f $(PID_FILE); \
echo "β
Stopped"; \
else \
echo "β οΈ PID file exists but process not running"; \
rm -f $(PID_FILE); \
fi; \
else \
echo "β No PID file found. Is the server running?"; \
echo "π‘ Check running processes: ps aux | grep gunicorn"; \
fi
serve-restart: serve-stop serve
@echo "β
MCP Gateway restarted"
serve-status:
@if [ -f $(PID_FILE) ] && kill -0 $$(cat $(PID_FILE)) 2>/dev/null; then \
echo "β
MCP Gateway is running"; \
echo " PID: $$(cat $(PID_FILE))"; \
echo " Port: $(SERVE_PORT)"; \
echo " Uptime: $$(ps -o etime= -p $$(cat $(PID_FILE)) | xargs)"; \
else \
echo "β MCP Gateway is not running"; \
if lsof -Pi :$(SERVE_PORT) -sTCP:LISTEN -t >/dev/null 2>&1; then \
echo "β οΈ But something else is using port $(SERVE_PORT)"; \
lsof -i :$(SERVE_PORT); \
fi; \
fi
2. Enhanced run-gunicorn.sh with instance detection:
#!/bin/bash
# run-gunicorn.sh
PID_FILE="/tmp/mcpgateway.pid"
PORT="${PORT:-4444}"
# Check if already running
if [ -f "$PID_FILE" ]; then
if kill -0 $(cat "$PID_FILE") 2>/dev/null; then
echo "β MCP Gateway already running (PID: $(cat $PID_FILE))"
exit 1
else
rm -f "$PID_FILE"
fi
fi
# Check port availability
if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then
echo "β Port $PORT is already in use"
exit 1
fi
# Start gunicorn and save PID
exec gunicorn mcpgateway.main:app \
--bind 0.0.0.0:$PORT \
--workers 4 \
--pid "$PID_FILE" \
...
3. Alternative: Use systemd-style lock file:
LOCK_DIR := /tmp/mcpgateway.lock
serve:
@mkdir $(LOCK_DIR) 2>/dev/null || { \
echo "β MCP Gateway appears to be running (lock exists)"; \
echo "π‘ If this is incorrect, run: make serve-force-unlock"; \
exit 1; \
}
@trap 'rmdir $(LOCK_DIR) 2>/dev/null' EXIT; \
./run-gunicorn.sh
β Acceptance Criteria
-
make serve
detects if an instance is already running - Clear error message with actionable options when instance exists
- Port conflict detection before attempting to start
-
make serve-stop
command to cleanly stop the server -
make serve-restart
command for convenience -
make serve-status
to check current state - PID file cleanup on both normal and abnormal exit
- Works across different shells (bash, zsh, etc.)
- Handles stale PID files gracefully
π§ͺ Testing Requirements
# Test 1: Normal operation
make serve # Should start
make serve-status # Should show running
make serve # Should fail with helpful message
make serve-stop # Should stop cleanly
# Test 2: Port conflict
python3 -m http.server 4444 & # Block the port
make serve # Should detect port conflict
# Test 3: Stale PID
make serve # Start
kill -9 $(cat /tmp/mcpgateway.pid) # Force kill
make serve # Should detect stale PID and start
# Test 4: Multiple terminals
# Terminal 1: make serve
# Terminal 2: make serve (should fail)
# Terminal 2: make serve-restart (should work)
π Additional Enhancements
- Process group management for better cleanup:
# Start in new process group
setsid ./run-gunicorn.sh & echo $! > $(PID_FILE)
# Kill entire process group
kill -TERM -$(cat $(PID_FILE))
- Automatic port selection if default is busy:
serve:
@PORT=$$(python3 -c "import socket; s=socket.socket(); s.bind(('',0)); print(s.getsockname()[1]); s.close()"); \
echo "π Starting on port $$PORT..."; \
PORT=$$PORT ./run-gunicorn.sh
- Health check after start:
serve:
@# ... start server ...
@echo "β³ Waiting for server to be ready..."
@for i in 1 2 3 4 5; do \
curl -s http://localhost:$(SERVE_PORT)/health >/dev/null && break || sleep 1; \
done
@curl -s http://localhost:$(SERVE_PORT)/health >/dev/null && \
echo "β
Server is healthy" || echo "β οΈ Server started but health check failed"
π§ Related Improvements
- Add similar checks to
make dev
target - Consider using
flock
for more robust locking - Add
make serve-logs
to tail the server logs - Integration with systemd/launchd for production use
Benefits:
- Prevents confusing multiple instance scenarios
- Saves developer time debugging port conflicts
- Cleaner process management
- Better error messages
- Easier troubleshooting
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingtriageIssues / Features awaiting triageIssues / Features awaiting triage