Fix V2 race conditions, auth gaps, serialization bugs, and async stats
Phase 1 - Critical Fixes: - Add game_lock (asyncio.Lock) to Room class for serializing mutations - Wrap all game action handlers in lock to prevent race conditions - Split Card.to_dict into to_dict (full data) and to_client_dict (hidden) - Fix CardState.from_dict to handle missing rank/suit gracefully - Fix GameOptions reconstruction in recovery_service (dict -> object) - Extend state cache TTL from 4h to 24h, add touch_game method Phase 2 - Security: - Add optional WebSocket authentication via token query param - Use authenticated user ID/name when available - Add auth support to spectator WebSocket endpoint Phase 3 - Performance: - Make stats processing async (fire-and-forget) to avoid blocking game completion notifications Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,9 +39,9 @@ class StateCache:
|
||||
ACTIVE_ROOMS_KEY = "golf:rooms:active"
|
||||
PLAYER_ROOM_KEY = "golf:player:{player_id}:room"
|
||||
|
||||
# TTLs
|
||||
ROOM_TTL = timedelta(hours=4) # Inactive rooms expire
|
||||
GAME_TTL = timedelta(hours=4)
|
||||
# TTLs - extended to 24 hours to prevent active games from expiring
|
||||
ROOM_TTL = timedelta(hours=24) # Inactive rooms expire
|
||||
GAME_TTL = timedelta(hours=24)
|
||||
|
||||
def __init__(self, redis_client: redis.Redis):
|
||||
"""
|
||||
@@ -360,6 +360,18 @@ class StateCache:
|
||||
|
||||
await pipe.execute()
|
||||
|
||||
async def touch_game(self, game_id: str) -> None:
|
||||
"""
|
||||
Refresh game TTL on any activity.
|
||||
|
||||
Call this on game actions to prevent active games from expiring.
|
||||
|
||||
Args:
|
||||
game_id: Game UUID to refresh.
|
||||
"""
|
||||
key = self.GAME_KEY.format(game_id=game_id)
|
||||
await self.redis.expire(key, int(self.GAME_TTL.total_seconds()))
|
||||
|
||||
|
||||
# Global state cache instance (initialized on first use)
|
||||
_state_cache: Optional[StateCache] = None
|
||||
|
||||
Reference in New Issue
Block a user