li / services /image_bytes_cache.py
xiaolongmr
fix: serve newly persisted images from bounded hot caches
3ab01a7
Raw History Blame Contribute Delete
1.78 kB
from __future__ import annotations
from collections import OrderedDict
from threading import Lock
import time
class ImageBytesCache:
"""Small TTL/LRU accelerator, never a replacement for durable originals."""
def __init__(self, *, max_bytes: int, max_entries: int, ttl: float = 300.0):
self.max_bytes = max_bytes
self.max_entries = max_entries
self.ttl = ttl
self._lock = Lock()
self._items: OrderedDict[str, tuple[float, bytes]] = OrderedDict()
self._bytes = 0
def get(self, key: str) -> bytes | None:
with self._lock:
item = self._items.get(key)
if item is None:
return None
expires, payload = item
if time.monotonic() >= expires:
self._items.pop(key)
self._bytes -= len(payload)
return None
self._items.move_to_end(key)
return payload
def put(self, key: str, payload: bytes) -> None:
if not payload or len(payload) > self.max_bytes:
self.discard(key)
return
with self._lock:
previous = self._items.pop(key, None)
if previous is not None:
self._bytes -= len(previous[1])
self._items[key] = (time.monotonic() + self.ttl, payload)
self._bytes += len(payload)
while self._bytes > self.max_bytes or len(self._items) > self.max_entries:
_, (_expires, evicted) = self._items.popitem(last=False)
self._bytes -= len(evicted)
def discard(self, key: str) -> None:
with self._lock:
previous = self._items.pop(key, None)
if previous is not None:
self._bytes -= len(previous[1])