Python
Public
Fork: Async LRU Cache with TTL and Eviction Callback
Implements an asynchronous Least Recently Used (LRU) cache with a fixed size, optional time-to-live (TTL) for entries, background expiration cleanup, and support for eviction event callbacks.
Python
import asyncio
import time
from collections import OrderedDict
class AsyncLRUCache:
def __init__(self, maxsize):
self.maxsize = maxsize
self.cache = OrderedDict() # key -> (value, expire_time)
self.lock = asyncio.Lock()
self._eviction_callback = None
self._cleanup_task = None
async def get(self, key):
async with self.lock:
if key not in self.cache:
return None
value, expire = self.cache[key]
if expire is not None and expire < time.time():
await self._evict(key, reason='expired')
return None
# Move to end to mark as recently used
self.cache.move_to_end(key)
return value
async def set(self, key, value, ttl_sec=None):
async with self.lock:
expire = time.time() + ttl_sec if ttl_sec is not None else None
if key in self.cache:
self.cache[key] = (value, expire)
self.cache.move_to_end(key)
else:
self.cache[key] = (value, expire)
if len(self.cache) > self.maxsize:
oldest_key = next(iter(self.cache))
await self._evict(oldest_key, reason='capacity')
async def _evict(self, key, reason):
if key in self.cache:
value, expire = self.cache.pop(key)
if self._eviction_callback:
await self._maybe_await(self._eviction_callback, key, value, reason)
async def _maybe_await(self, func, *args, **kwargs):
result = func(*args, **kwargs)
if asyncio.iscoroutine(result):
await result
def register_eviction_callback(self, callback):
self._eviction_callback = callback
async def _cleanup(self):
while True:
async with self.lock:
keys_to_evict = []
now = time.time()
for key, (value, expire) in list(self.cache.items()):
if expire is not None and expire < now:
keys_to_evict.append(key)
for key in keys_to_evict:
await self._evict(key, reason='expired')
await asyncio.sleep(1)
def start_auto_cleanup(self):
if self._cleanup_task is None or self._cleanup_task.done():
self._cleanup_task = asyncio.create_task(self._cleanup())
def stop_auto_cleanup(self):
if self._cleanup_task and not self._cleanup_task.done():
self._cleanup_task.cancel()