Commit 906e5cc3 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

perf: QA Gatekeeper audit fixes for backend concurrency and thread-safety

parent 24d87940
......@@ -269,7 +269,7 @@ def _get_colors_by_group(group: str, color_groups_map: dict, exclude: str | None
@router.get("/{code}")
async def get_fashion_matches(code: str):
def get_fashion_matches(code: str):
from worker.stylist_engine import StylistEngine
engine = StylistEngine()
......@@ -426,7 +426,7 @@ async def color_logic(req: ColorLogicRequest):
@router.post("/outfit-suggest")
async def outfit_suggest(req: OutfitSuggestRequest):
def outfit_suggest(req: OutfitSuggestRequest):
try:
rules = _load_rules()
color_keys = rules.get("color_keys", {})
......@@ -531,7 +531,7 @@ class ScoreTestRequest(BaseModel):
@router.post("/score-test")
async def score_test(req: ScoreTestRequest):
def score_test(req: ScoreTestRequest):
try:
from worker.stylist_engine import StylistEngine
......@@ -569,7 +569,7 @@ async def score_test(req: ScoreTestRequest):
@router.get("/audit/tag-coverage")
async def audit_tag_coverage(limit: int = Query(300, ge=20, le=2000), q: str = ""):
def audit_tag_coverage(limit: int = Query(300, ge=20, le=2000), q: str = ""):
"""Audit product tags vs rules coverage for data QA."""
try:
from worker.stylist_engine import StylistEngine
......@@ -745,7 +745,7 @@ async def audit_tag_coverage(limit: int = Query(300, ge=20, le=2000), q: str = "
# --- Rules Framework HTML View ---
@router.get("/rules/view")
async def rules_view():
def rules_view():
from collections import defaultdict
from fastapi.responses import HTMLResponse
......
......@@ -6,6 +6,7 @@ Based on chatbot-rsa pattern
import asyncio
import logging
import os
import threading
from typing import Any
import aiomysql
......@@ -47,8 +48,8 @@ get_db_connection = _manager.get_connection
class StarRocksConnection:
# Shared connection (Singleton-like behavior) for all instances
_shared_conn = None
# Use thread-local storage to ensure thread-safety across FastAPI sync workers
_thread_local = threading.local()
def __init__(
self,
......@@ -71,16 +72,17 @@ class StarRocksConnection:
"""
Establish or reuse persistent connection.
"""
# 1. Try to reuse existing shared connection
if StarRocksConnection._shared_conn and StarRocksConnection._shared_conn.open:
# 1. Try to reuse existing thread-local connection
conn = getattr(StarRocksConnection._thread_local, 'conn', None)
if conn and conn.open:
try:
# Ping to check if alive, reconnect if needed
StarRocksConnection._shared_conn.ping(reconnect=True)
self.conn = StarRocksConnection._shared_conn
conn.ping(reconnect=True)
self.conn = conn
return self.conn
except Exception as e:
logger.warning(f"⚠️ Connection lost, reconnecting: {e}")
StarRocksConnection._shared_conn = None
StarRocksConnection._thread_local.conn = None
# 2. Create new connection if needed
print(f" [DB] 🔌 Đang kết nối StarRocks (New Session): {self.host}:{self.port}...")
......@@ -101,8 +103,8 @@ class StarRocksConnection:
print(" [DB] ✅ Kết nối thành công.")
logger.info("✅ Connected to StarRocks")
# Save to class variable
StarRocksConnection._shared_conn = new_conn
# Save to thread-local variable
StarRocksConnection._thread_local.conn = new_conn
self.conn = new_conn
except Exception as e:
......@@ -136,7 +138,7 @@ class StarRocksConnection:
print(f" [DB] ❌ Lỗi truy vấn: {e!s}")
logger.error(f"❌ StarRocks query error: {e}")
# Incase of query error due to connection, invalidate it
StarRocksConnection._shared_conn = None
StarRocksConnection._thread_local.conn = None
raise
# FINALLY BLOCK REMOVED: Do NOT close connection
......@@ -264,7 +266,8 @@ class StarRocksConnection:
def close(self):
"""Explicitly close if needed (e.g. app shutdown)"""
if StarRocksConnection._shared_conn and StarRocksConnection._shared_conn.open:
StarRocksConnection._shared_conn.close()
StarRocksConnection._shared_conn = None
conn = getattr(StarRocksConnection._thread_local, 'conn', None)
if conn and conn.open:
conn.close()
StarRocksConnection._thread_local.conn = None
self.conn = None
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment