cleanup hot path

This commit is contained in:
2025-09-18 14:45:59 +02:00
parent e8742e1948
commit 745a72aedb
3 changed files with 92 additions and 57 deletions

View File

@@ -1,6 +1,5 @@
"""Authentication API endpoints"""
import asyncio
import logging
from datetime import datetime, timedelta
from typing import Optional
@@ -172,54 +171,6 @@ async def login(
bcrypt_rounds=settings.BCRYPT_ROUNDS,
)
# DEBUG: Check Redis connection (keep lightweight to avoid request stalls)
if settings.APP_DEBUG:
try:
logger.info("LOGIN_REDIS_CHECK_START", redis_url=redis_url)
import redis.asyncio as redis
redis_url = settings.REDIS_URL
logger.info(f"Redis URL: {redis_url}")
redis_client = redis.from_url(
redis_url,
socket_connect_timeout=1.0,
socket_timeout=1.0,
)
test_start = datetime.utcnow()
try:
await asyncio.wait_for(redis_client.ping(), timeout=1.5)
test_end = datetime.utcnow()
logger.info(
"LOGIN_REDIS_CHECK_SUCCESS",
duration_seconds=(test_end - test_start).total_seconds(),
)
except asyncio.TimeoutError:
logger.warning("LOGIN_REDIS_CHECK_TIMEOUT", timeout_seconds=1.5)
finally:
await redis_client.close()
except Exception as e:
logger.error("LOGIN_REDIS_CHECK_FAILURE", error=str(e))
# DEBUG: Check database connection with timeout
if settings.APP_DEBUG:
try:
logger.info("LOGIN_DB_PING_START")
test_start = datetime.utcnow()
await asyncio.wait_for(db.execute(select(1)), timeout=3.0)
test_end = datetime.utcnow()
logger.info(
"LOGIN_DB_PING_SUCCESS",
duration_seconds=(test_end - test_start).total_seconds(),
)
except asyncio.TimeoutError:
logger.warning("LOGIN_DB_PING_TIMEOUT", timeout_seconds=3.0)
except Exception as e:
logger.error("LOGIN_DB_PING_FAILURE", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Database connection error"
)
start_time = datetime.utcnow()
# Get user by email

View File

@@ -2,6 +2,8 @@
Security utilities for authentication and authorization
"""
import asyncio
import concurrent.futures
import logging
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
@@ -33,19 +35,29 @@ security = HTTPBearer()
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verify a password against its hash"""
import time
start_time = time.time()
logger.info(f"=== PASSWORD VERIFICATION START === BCRYPT_ROUNDS: {settings.BCRYPT_ROUNDS}")
try:
result = pwd_context.verify(plain_password, hashed_password)
# Run password verification in a thread with timeout
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(pwd_context.verify, plain_password, hashed_password)
result = future.result(timeout=5.0) # 5 second timeout
end_time = time.time()
duration = end_time - start_time
logger.info(f"=== PASSWORD VERIFICATION END === Duration: {duration:.3f}s, Result: {result}")
if duration > 5:
if duration > 1:
logger.warning(f"PASSWORD VERIFICATION TOOK TOO LONG: {duration:.3f}s")
return result
except concurrent.futures.TimeoutError:
end_time = time.time()
duration = end_time - start_time
logger.error(f"=== PASSWORD VERIFICATION TIMEOUT === Duration: {duration:.3f}s")
return False # Treat timeout as verification failure
except Exception as e:
end_time = time.time()
duration = end_time - start_time

View File

@@ -1,9 +1,9 @@
"""
Main FastAPI application entry point
"""
"""Main FastAPI application entry point"""
import asyncio
import logging
import sys
import time
from contextlib import asynccontextmanager
from fastapi import FastAPI
@@ -14,10 +14,13 @@ from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException
from starlette.middleware.sessions import SessionMiddleware
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
from app.core.config import settings
from app.core.logging import setup_logging
from app.core.security import get_current_user
from app.db.database import init_db
from app.db.database import init_db, async_session_factory
from app.api.internal_v1 import internal_api_router
from app.api.public_v1 import public_api_router
from app.utils.exceptions import CustomHTTPException
@@ -32,6 +35,68 @@ setup_logging()
logger = logging.getLogger(__name__)
async def _check_redis_startup():
"""Validate Redis connectivity during startup."""
if not settings.REDIS_URL:
logger.info("Startup Redis check skipped: REDIS_URL not configured")
return
try:
import redis.asyncio as redis
except ModuleNotFoundError:
logger.warning("Startup Redis check skipped: redis library not installed")
return
client = redis.from_url(
settings.REDIS_URL,
socket_connect_timeout=1.0,
socket_timeout=1.0,
)
start = time.perf_counter()
try:
await asyncio.wait_for(client.ping(), timeout=3.0)
duration = time.perf_counter() - start
logger.info(
"Startup Redis check succeeded",
extra={"redis_url": settings.REDIS_URL, "duration_seconds": round(duration, 3)},
)
except Exception as exc: # noqa: BLE001
logger.warning(
"Startup Redis check failed",
extra={"error": str(exc), "redis_url": settings.REDIS_URL},
)
finally:
await client.close()
async def _check_database_startup():
"""Validate database connectivity during startup."""
start = time.perf_counter()
try:
async with async_session_factory() as session:
await asyncio.wait_for(session.execute(select(1)), timeout=3.0)
duration = time.perf_counter() - start
logger.info(
"Startup database check succeeded",
extra={"duration_seconds": round(duration, 3)},
)
except (asyncio.TimeoutError, SQLAlchemyError) as exc:
logger.error(
"Startup database check failed",
extra={"error": str(exc)},
)
raise
async def run_startup_dependency_checks():
"""Run dependency checks once during application startup."""
logger.info("Running startup dependency checks...")
await _check_redis_startup()
await _check_database_startup()
logger.info("Startup dependency checks complete")
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
@@ -46,7 +111,14 @@ async def lifespan(app: FastAPI):
logger.info("Core cache service initialized successfully")
except Exception as e:
logger.warning(f"Core cache service initialization failed: {e}")
# Run one-time dependency checks (non-blocking for auth requests)
try:
await run_startup_dependency_checks()
except Exception:
logger.error("Critical dependency check failed during startup")
raise
# Initialize database
await init_db()
@@ -252,4 +324,4 @@ if __name__ == "__main__":
port=settings.APP_PORT,
reload=settings.APP_DEBUG,
log_level=settings.APP_LOG_LEVEL.lower(),
)
)