Actualizar backend/cache_manager.py

This commit is contained in:
2026-09-10 11:29:18 -05:00
parent 85ea808481
commit 90f91e70cd

View File

@@ -8,6 +8,34 @@ _CACHE: Dict[str, Tuple[float, Any]] = {}
_LOCK = threading.Lock()
_DEFAULT_TTL = 1800 # 30 min
# ── Lock POR CLAVE (evita "estampida"): si varias peticiones piden el MISMO dato
# vencido al mismo tiempo, solo la PRIMERA ejecuta la consulta pesada; las demas
# esperan ese resultado en vez de lanzar su propia copia (que saturaba la base y
# hacia que todo se sintiera colgado). Cada clave tiene su propio candado.
_KEY_LOCKS: Dict[str, threading.Lock] = {}
_KEY_LOCKS_GUARD = threading.Lock()
def _lock_de(key: str) -> threading.Lock:
with _KEY_LOCKS_GUARD:
lk = _KEY_LOCKS.get(key)
if lk is None:
lk = threading.Lock()
_KEY_LOCKS[key] = lk
return lk
# ── Modo "recarga en 2do plano" ────────────────────────────────────────────
# Mientras esta activo (solo lo activa el refresco de los 30 min, en su hilo),
# cache_get_or_set RECALCULA cada dato con info fresca y recien al terminar
# reemplaza la entrada. Asi la cache nunca queda vacia: el usuario sigue viendo
# los datos viejos hasta que TODO lo nuevo esta listo (swap = 1 parpadeo).
_FORZAR = threading.local()
def _forzar_activo() -> bool:
return getattr(_FORZAR, "on", False)
def set_forzar(on: bool) -> None:
_FORZAR.on = on
def _make_key(prefix: str, args: tuple) -> str:
return prefix + ":" + "|".join(str(a) for a in args)
@@ -16,14 +44,30 @@ def _make_key(prefix: str, args: tuple) -> str:
def cache_get_or_set(prefix: str, args: tuple, loader: Callable[[], Any], ttl: int = _DEFAULT_TTL) -> Any:
key = _make_key(prefix, args)
now = time.time()
if _forzar_activo():
# Refresco en 2do plano: recalcula y sobrescribe SIN pasar por el lock de
# usuario (no debe esperar a nadie ni bloquear a los que leen la cache).
value = loader()
with _LOCK:
_CACHE[key] = (time.time(), value)
return value
# Lectura normal: si esta fresco, devolver YA.
with _LOCK:
hit = _CACHE.get(key)
if hit and (now - hit[0] < ttl):
return hit[1]
value = loader()
with _LOCK:
_CACHE[key] = (now, value)
return value
# Vencido/ausente: solo UNA peticion calcula; las demas esperan (lock x clave).
with _lock_de(key):
with _LOCK:
hit = _CACHE.get(key)
if hit and (time.time() - hit[0] < ttl):
return hit[1]
value = loader()
with _LOCK:
_CACHE[key] = (time.time(), value)
return value
def cache_keys():