fix: game service api block when a game is running

This commit is contained in:
2026-05-13 21:26:46 +08:00
parent 3c027eae0b
commit 79dccde963
7 changed files with 378 additions and 73 deletions
+51 -13
View File
@@ -5,13 +5,14 @@ from threading import RLock
from typing import Any
from uuid import uuid4
from texas_holdem.agents import build_agent
from texas_holdem.agents import build_agent, http_agent_endpoint_from_spec
from texas_holdem.engine import TableGame
class GameManager:
def __init__(self) -> None:
self._games: dict[str, TableGame] = {}
self._http_endpoint_owners: dict[str, str] = {}
self._lock = RLock()
def create_game(self, payload: dict[str, Any]) -> TableGame:
@@ -29,12 +30,19 @@ class GameManager:
big_blind = int(payload.get("big_blind", 10))
specs = []
http_endpoints: set[str] = set()
for seat, raw_spec in enumerate(players):
if not isinstance(raw_spec, dict):
raise ValueError("each player must be an object")
player_id = str(raw_spec.get("id") or raw_spec.get("player_id") or f"p{seat + 1}")
name = str(raw_spec.get("name") or player_id)
agent = build_agent(raw_spec.get("agent", raw_spec), rng)
agent_spec = raw_spec.get("agent", raw_spec)
if not isinstance(agent_spec, dict):
raise ValueError("agent spec must be an object")
endpoint = http_agent_endpoint_from_spec(agent_spec)
if endpoint is not None:
http_endpoints.add(endpoint)
agent = build_agent(agent_spec, rng, player_id=player_id)
specs.append((player_id, name, agent))
game = TableGame(
@@ -46,9 +54,18 @@ class GameManager:
rng=rng,
)
with self._lock:
self._release_completed_http_endpoints_locked()
if game_id in self._games:
raise ValueError(f"game already exists: {game_id}")
for endpoint in http_endpoints:
owner = self._http_endpoint_owners.get(endpoint)
if owner is not None and owner != game_id:
raise ValueError(
f"http agent endpoint already belongs to game {owner}: {endpoint}"
)
self._games[game_id] = game
for endpoint in http_endpoints:
self._http_endpoint_owners[endpoint] = game_id
return game
def get_game(self, game_id: str) -> TableGame:
@@ -58,9 +75,13 @@ class GameManager:
except KeyError as exc:
raise KeyError(f"game not found: {game_id}") from exc
def get_game_state(self, game_id: str) -> dict[str, object]:
return self.get_game(game_id).snapshot_completed()
def list_games(self) -> list[dict[str, object]]:
with self._lock:
return [game.to_dict() for game in self._games.values()]
games = list(self._games.values())
return [game.snapshot_completed() for game in games]
def run_hands(
self,
@@ -78,13 +99,30 @@ class GameManager:
no-argument behaviour.
"""
game = self.get_game(game_id)
with self._lock:
return [
summary.to_dict()
for summary in game.run_hands(
count,
until_one_left=until_one_left,
small_blind=small_blind,
big_blind=big_blind,
)
]
summaries = [
summary.to_dict()
for summary in game.run_hands(
count,
until_one_left=until_one_left,
small_blind=small_blind,
big_blind=big_blind,
)
]
if game.is_complete:
with self._lock:
self._release_http_endpoints_for_game_locked(game_id)
return summaries
def _release_completed_http_endpoints_locked(self) -> None:
for game_id, game in list(self._games.items()):
if game.lock.acquire(blocking=False):
try:
if game.is_complete:
self._release_http_endpoints_for_game_locked(game_id)
finally:
game.lock.release()
def _release_http_endpoints_for_game_locked(self, game_id: str) -> None:
for endpoint, owner in list(self._http_endpoint_owners.items()):
if owner == game_id:
del self._http_endpoint_owners[endpoint]