prioritize interactive requests over background checks in shared Lichess-token queue
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 15s

/today, /yesterday, /week were queuing behind the entire periodic-check
backlog on the same rate-limited LICHESS_APP_TOKEN (observed ~2min wait on
prod for a single command). Add a two-tier priority queue to SharedTokenGate:
interactive on-demand requests jump ahead of background periodic checks,
which still drain normally when nothing interactive is waiting. Verified
locally end-to-end (isolated queue unit tests, live request with priority
correctly reaching Lichess, and a real successful accuracy fetch rendered
through StatsFormatter).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
vrubelroman 2026-07-26 10:25:34 +00:00
parent 0dbd0c400b
commit d4eb61f994
6 changed files with 76 additions and 25 deletions

View file

@ -84,11 +84,16 @@ class LichessClient:
headers = {'Authorization': f'Bearer {self.app_token}'} if self.app_token else {}
# Держим shared_token_gate на весь запрос-ответ и с паузой после —
# LICHESS_APP_TOKEN общий с get_games_of_period, и Lichess не
# пускает 2+ запроса подряд без ощутимой паузы между ними.
async with self.shared_token_gate:
# пускает 2+ запроса подряд без ощутимой паузы между ними — держим
# shared_token_gate на весь запрос-ответ. get_user_activity вызывается
# только из интерактивных команд (/today и т.п.), периодические проверки
# его не используют — приоритет всегда "interactive", параметризовать не нужно.
await self.shared_token_gate.acquire(priority="interactive")
try:
response = await self.client.get(url, headers=headers)
finally:
self.shared_token_gate.release()
logger.info(f"🔍 Lichess API response status: {response.status_code} for {username}")
response.raise_for_status() # Проверяем статус ответа
@ -113,19 +118,22 @@ class LichessClient:
logger.error(traceback.format_exc())
raise
async def get_games_of_period(self, username: str, since_ms: int, until_ms: int, rated_only: bool = True) -> Optional[List[Dict[str, Any]]]:
async def get_games_of_period(self, username: str, since_ms: int, until_ms: int, rated_only: bool = True, priority: str = "background") -> Optional[List[Dict[str, Any]]]:
"""
Получает игры пользователя за определенный период.
Lichess API возвращает игры в формате NDJSON (Newline Delimited JSON),
где каждая строка содержит JSON объект с информацией об игре.
Args:
username: Имя пользователя на Lichess
since_ms: Начало периода в миллисекундах (Unix timestamp * 1000)
until_ms: Конец периода в миллисекундах (Unix timestamp * 1000)
rated_only: Только рейтинговые игры (по умолчанию True)
priority: "high" для интерактивных команд (/today и т.п.), чтобы не
ждать позади бэклога фоновых периодических проверок в общей
очереди shared_token_gate; "background" (по умолчанию) для них.
Returns:
Список игр в формате JSON или None при ошибке
@ -157,15 +165,20 @@ class LichessClient:
if self.app_token:
headers['Authorization'] = f'Bearer {self.app_token}'
logger.info(f"Запрос игр для {username} с {since_ms} по {until_ms}")
logger.info(f"Запрос игр для {username} с {since_ms} по {until_ms} (priority={priority})")
# Держим shared_token_gate на весь запрос-ответ и с паузой после —
# LICHESS_APP_TOKEN общий с get_user_activity, и Lichess не пускает
# 2+ запроса подряд без ощутимой паузы между ними (даже строго
# последовательные с интервалом ~100-300мс ловили 429 "Please only
# run 1 request(s) at a time" под реальной нагрузкой на проде).
async with self.shared_token_gate:
# priority="interactive" (например, довесочный accuracy-fetch в /today)
# обгоняет фоновые периодические проверки в этой же очереди.
await self.shared_token_gate.acquire(priority=priority)
try:
response = await self.client.get(url, params=params, headers=headers)
finally:
self.shared_token_gate.release()
response.raise_for_status() # Проверяем статус ответа
# Парсим NDJSON (Newline Delimited JSON)