fix(fetcher): handle token timeout gracefully
This commit is contained in:
@@ -3,10 +3,13 @@ from typing import Annotated
|
||||
from app.config import settings
|
||||
from app.dependencies.database import get_redis
|
||||
from app.fetcher import Fetcher as OriginFetcher
|
||||
from app.fetcher._base import TokenAuthError
|
||||
from app.log import fetcher_logger
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
fetcher: OriginFetcher | None = None
|
||||
logger = fetcher_logger("FetcherDependency")
|
||||
|
||||
|
||||
async def get_fetcher() -> OriginFetcher:
|
||||
@@ -24,7 +27,14 @@ async def get_fetcher() -> OriginFetcher:
|
||||
if access_token:
|
||||
fetcher.access_token = str(access_token)
|
||||
# Always ensure the access token is valid, regardless of initial state
|
||||
try:
|
||||
await fetcher.ensure_valid_access_token()
|
||||
except TokenAuthError as exc:
|
||||
logger.warning(
|
||||
f"Failed to refresh fetcher access token during startup: {exc}. Will retry on demand."
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.exception("Unexpected error while initializing fetcher access token", exc_info=exc)
|
||||
return fetcher
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import time
|
||||
from app.dependencies.database import get_redis
|
||||
from app.log import fetcher_logger
|
||||
|
||||
from httpx import AsyncClient, HTTPStatusError
|
||||
from httpx import AsyncClient, HTTPStatusError, TimeoutException
|
||||
|
||||
|
||||
class TokenAuthError(Exception):
|
||||
@@ -159,8 +159,11 @@ class BaseFetcher:
|
||||
return True
|
||||
return self.token_expiry <= int(time.time()) or not self.access_token
|
||||
|
||||
async def grant_access_token(self) -> None:
|
||||
async with AsyncClient() as client:
|
||||
async def grant_access_token(self, retries: int = 3, backoff: float = 1.0) -> None:
|
||||
last_error: Exception | None = None
|
||||
async with AsyncClient(timeout=30.0) as client:
|
||||
for attempt in range(1, retries + 1):
|
||||
try:
|
||||
response = await client.post(
|
||||
"https://osu.ppy.sh/oauth/token",
|
||||
data={
|
||||
@@ -188,6 +191,30 @@ class BaseFetcher:
|
||||
logger.success(
|
||||
f"Granted new access token for client {self.client_id}, expires in {token_data['expires_in']} seconds"
|
||||
)
|
||||
return
|
||||
|
||||
except TimeoutException as exc:
|
||||
last_error = exc
|
||||
logger.warning(
|
||||
f"Timed out while requesting access token for client {self.client_id} (attempt {attempt}/{retries})"
|
||||
)
|
||||
except HTTPStatusError as exc:
|
||||
last_error = exc
|
||||
logger.warning(
|
||||
f"HTTP error while requesting access token for client {self.client_id}"
|
||||
f" (status: {exc.response.status_code}, attempt {attempt}/{retries})"
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
last_error = exc
|
||||
logger.exception(
|
||||
f"Unexpected error while requesting access token for client {self.client_id}"
|
||||
f" (attempt {attempt}/{retries})"
|
||||
)
|
||||
|
||||
if attempt < retries:
|
||||
await asyncio.sleep(backoff * attempt)
|
||||
|
||||
raise TokenAuthError("Failed to grant access token after retries") from last_error
|
||||
|
||||
async def ensure_valid_access_token(self) -> None:
|
||||
if self.is_token_expired():
|
||||
|
||||
Reference in New Issue
Block a user