Add CPU profile cleanup on shutdown and debug endpoints

- Reset all CPU profiles on server shutdown to prevent stuck profiles
- Clean up all rooms during shutdown
- Add GET /api/debug/cpu-profiles to check allocation status
- Add POST /api/debug/reset-cpu-profiles for emergency cleanup

This fixes the issue where CPU profiles get "stuck" when connections
drop without clean teardown, preventing new games from adding CPUs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee 2026-01-27 19:08:14 -05:00
parent 15135c404e
commit 724bf87c43

View File

@ -15,7 +15,7 @@ import redis.asyncio as redis
from config import config from config import config
from room import RoomManager, Room from room import RoomManager, Room
from game import GamePhase, GameOptions from game import GamePhase, GameOptions
from ai import GolfAI, process_cpu_turn, get_all_profiles from ai import GolfAI, process_cpu_turn, get_all_profiles, reset_all_profiles
from game_log import get_logger from game_log import get_logger
# Import production components # Import production components
@ -193,6 +193,14 @@ async def lifespan(app: FastAPI):
# Close all WebSocket connections gracefully # Close all WebSocket connections gracefully
await _close_all_websockets() await _close_all_websockets()
# Clean up all rooms and release CPU profiles
for room in list(room_manager.rooms.values()):
for cpu in list(room.get_cpu_players()):
room.remove_player(cpu.id)
room_manager.rooms.clear()
reset_all_profiles()
logger.info("All rooms and CPU profiles cleaned up")
# Cancel background tasks # Cancel background tasks
if _leaderboard_refresh_task: if _leaderboard_refresh_task:
_leaderboard_refresh_task.cancel() _leaderboard_refresh_task.cancel()
@ -392,6 +400,37 @@ async def require_admin(user: User = Depends(require_user)) -> User:
return user return user
# =============================================================================
# Debug Endpoints (CPU Profile Management)
# =============================================================================
@app.get("/api/debug/cpu-profiles")
async def get_cpu_profile_status():
"""Get current CPU profile allocation status."""
from ai import _used_profiles, _cpu_profiles, CPU_PROFILES
return {
"total_profiles": len(CPU_PROFILES),
"used_count": len(_used_profiles),
"used_profiles": list(_used_profiles),
"cpu_mappings": {cpu_id: profile.name for cpu_id, profile in _cpu_profiles.items()},
"active_rooms": len(room_manager.rooms),
"rooms": {
code: {
"players": len(room.players),
"cpu_players": [p.name for p in room.get_cpu_players()],
}
for code, room in room_manager.rooms.items()
},
}
@app.post("/api/debug/reset-cpu-profiles")
async def reset_cpu_profiles():
"""Reset all CPU profiles (emergency cleanup)."""
reset_all_profiles()
return {"status": "ok", "message": "All CPU profiles reset"}
@app.websocket("/ws") @app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket): async def websocket_endpoint(websocket: WebSocket):
await websocket.accept() await websocket.accept()