| """Async-safe TTL-LRU cache.""" |
|
|
| from __future__ import annotations |
|
|
| import asyncio |
| import hashlib |
| import json |
| import time |
| from collections import OrderedDict |
|
|
| from app.config import cfg |
|
|
|
|
| class TTLCache: |
| """Async-safe LRU cache with per-entry TTL.""" |
|
|
| def __init__(self, maxsize: int = 256, ttl: int = 3600): |
| self._cache: OrderedDict = OrderedDict() |
| self._maxsize = maxsize |
| self._ttl = ttl |
| self._lock = asyncio.Lock() |
|
|
| def _key(self, *args) -> str: |
| payload = json.dumps(args, ensure_ascii=False, sort_keys=True) |
| return hashlib.sha256(payload.encode()).hexdigest()[:20] |
|
|
| async def get(self, *args): |
| async with self._lock: |
| k = self._key(*args) |
| if k in self._cache: |
| value, ts = self._cache[k] |
| if time.monotonic() - ts < self._ttl: |
| self._cache.move_to_end(k) |
| return value |
| del self._cache[k] |
| return None |
|
|
| async def set(self, value, *args): |
| async with self._lock: |
| k = self._key(*args) |
| self._cache[k] = (value, time.monotonic()) |
| self._cache.move_to_end(k) |
| if len(self._cache) > self._maxsize: |
| self._cache.popitem(last=False) |
|
|
|
|
| search_cache = TTLCache(maxsize=cfg.CACHE_SIZE, ttl=cfg.CACHE_TTL) |
| analysis_cache = TTLCache(maxsize=cfg.CACHE_SIZE, ttl=cfg.CACHE_TTL) |
| rewrite_cache = TTLCache(maxsize=cfg.CACHE_SIZE, ttl=cfg.CACHE_TTL * 6) |
|
|