serialize shared-token lichess.org requests to fix accuracy dropping again
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 15s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 15s
games/user and user/activity share one LICHESS_APP_TOKEN process-wide, and Lichess allows only 1 concurrent request per token there (confirmed live: 429 "Please only run 1 request(s) at a time" under concurrent traffic). The existing rate limiter only paced request starts 0.2s apart without holding the lock through the request itself, so concurrent games/period calls (periodic checks vs. on-demand /today etc.) could still collide and get 429'd, silently dropping the accuracy shown in /today, /yesterday, /week (that fetch is best-effort and swallows errors). Hold a dedicated lock for the full request/response cycle on these two calls; puzzle requests use per-user tokens and don't need it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
7ef5875f58
commit
31e6fdd23d
2 changed files with 32 additions and 7 deletions
|
|
@ -19,7 +19,7 @@ from typing import List, Dict, Any, Optional
|
|||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import json
|
||||
from rate_limiter import get_rate_limiter
|
||||
from rate_limiter import get_rate_limiter, get_shared_token_lock
|
||||
|
||||
# Настройка логирования для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -45,6 +45,10 @@ class LichessClient:
|
|||
self.base_url = "https://lichess.org/api" # Базовый URL Lichess API
|
||||
self.client = httpx.AsyncClient(timeout=30.0) # HTTP клиент с таймаутом
|
||||
self.rate_limiter = get_rate_limiter()
|
||||
# Сериализует games/user и user/activity между собой (см. rate_limiter.py) —
|
||||
# обе шарят один LICHESS_APP_TOKEN, и Lichess не пускает по нему 2+
|
||||
# запроса одновременно.
|
||||
self.shared_token_lock = get_shared_token_lock()
|
||||
# Lichess начал возвращать 404 (вместо честного 401/429) на анонимные
|
||||
# запросы к games/user и user/activity — тот же валидный токен, отправленный
|
||||
# для другого юзера, сразу превращает 404 в 429, то есть эндпоинт жив, просто
|
||||
|
|
@ -80,7 +84,10 @@ class LichessClient:
|
|||
|
||||
headers = {'Authorization': f'Bearer {self.app_token}'} if self.app_token else {}
|
||||
|
||||
# Выполняем HTTP GET запрос
|
||||
# Держим shared_token_lock на весь запрос-ответ (не только на паузу
|
||||
# выше) — LICHESS_APP_TOKEN общий с get_games_of_period, и Lichess
|
||||
# не пускает по нему 2+ запроса одновременно.
|
||||
async with self.shared_token_lock:
|
||||
response = await self.client.get(url, headers=headers)
|
||||
logger.info(f"🔍 Lichess API response status: {response.status_code} for {username}")
|
||||
response.raise_for_status() # Проверяем статус ответа
|
||||
|
|
@ -152,7 +159,11 @@ class LichessClient:
|
|||
|
||||
logger.info(f"Запрос игр для {username} с {since_ms} по {until_ms}")
|
||||
|
||||
# Выполняем HTTP GET запрос
|
||||
# Держим shared_token_lock на весь запрос-ответ (не только на паузу
|
||||
# выше) — LICHESS_APP_TOKEN общий с get_user_activity, и Lichess
|
||||
# не пускает по нему 2+ запроса одновременно (наблюдали 429
|
||||
# "Please only run 1 request(s) at a time" под конкурентной нагрузкой).
|
||||
async with self.shared_token_lock:
|
||||
response = await self.client.get(url, params=params, headers=headers)
|
||||
response.raise_for_status() # Проверяем статус ответа
|
||||
|
||||
|
|
|
|||
|
|
@ -51,3 +51,17 @@ def get_rate_limiter() -> RateLimiter:
|
|||
"""Get the global rate limiter instance"""
|
||||
return _rate_limiter
|
||||
|
||||
# wait_if_needed() only paces request STARTS 0.2s apart; it releases the lock
|
||||
# before the request itself runs, so two requests can still be in flight at
|
||||
# once if either takes longer than 0.2s. That's fine for per-user-token calls
|
||||
# (each pair has its own identity), but games/user and user/activity share one
|
||||
# LICHESS_APP_TOKEN process-wide, and Lichess enforces a hard "1 request at a
|
||||
# time" limit per token there (observed as 429 "Please only run 1 request(s)
|
||||
# at a time" under concurrent traffic) — those two calls must hold this lock
|
||||
# for their entire request/response cycle, not just the pre-request wait.
|
||||
_shared_token_lock = asyncio.Lock()
|
||||
|
||||
def get_shared_token_lock() -> asyncio.Lock:
|
||||
"""Lock serializing lichess.org calls made with the shared LICHESS_APP_TOKEN."""
|
||||
return _shared_token_lock
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue