mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 07:24:34 +01:00
cleanup hot path
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user