110 lines
2.8 KiB
Python
110 lines
2.8 KiB
Python
"""简单的跨进程文件缓存。
|
|
|
|
Gunicorn worker 共享缓存文件;任一进程删除或重建缓存后,其他 worker
|
|
会通过文件签名变化立即放弃旧的内存副本。
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import pickle
|
|
import tempfile
|
|
import time
|
|
|
|
_cache = {}
|
|
_file_signatures = {}
|
|
CACHE_DIR = os.path.join(
|
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
'tmp_cache',
|
|
'simple',
|
|
)
|
|
|
|
|
|
def _path(key):
|
|
os.makedirs(CACHE_DIR, exist_ok=True)
|
|
filename = hashlib.md5(key.encode('utf-8')).hexdigest() + '.cache'
|
|
return os.path.join(CACHE_DIR, filename)
|
|
|
|
|
|
def _signature(path):
|
|
try:
|
|
stat = os.stat(path)
|
|
return stat.st_ino, stat.st_mtime_ns, stat.st_size
|
|
except OSError:
|
|
return None
|
|
|
|
|
|
def get(key):
|
|
path = _path(key)
|
|
current_signature = _signature(path)
|
|
entry = _cache.get(key)
|
|
|
|
if entry is not None and current_signature == _file_signatures.get(key) and current_signature is not None:
|
|
if entry['expire'] > time.time():
|
|
return entry['data']
|
|
delete(key)
|
|
return None
|
|
|
|
# 文件已被其他 worker 删除或替换,不能继续使用本进程的旧副本。
|
|
_cache.pop(key, None)
|
|
_file_signatures.pop(key, None)
|
|
if current_signature is None:
|
|
return None
|
|
|
|
try:
|
|
with open(path, 'rb') as f:
|
|
entry = pickle.load(f)
|
|
except (OSError, EOFError, pickle.PickleError, ValueError, TypeError):
|
|
return None
|
|
|
|
if not isinstance(entry, dict) or entry.get('expire', 0) <= time.time():
|
|
delete(key)
|
|
return None
|
|
|
|
_cache[key] = entry
|
|
_file_signatures[key] = _signature(path)
|
|
return entry['data']
|
|
|
|
|
|
def set(key, data, ttl=60):
|
|
entry = {'data': data, 'expire': time.time() + ttl}
|
|
path = _path(key)
|
|
fd, temp_path = tempfile.mkstemp(dir=CACHE_DIR, suffix='.tmp')
|
|
try:
|
|
with os.fdopen(fd, 'wb') as f:
|
|
pickle.dump(entry, f, protocol=pickle.HIGHEST_PROTOCOL)
|
|
os.replace(temp_path, path)
|
|
except Exception:
|
|
try:
|
|
os.unlink(temp_path)
|
|
except OSError:
|
|
pass
|
|
raise
|
|
_cache[key] = entry
|
|
_file_signatures[key] = _signature(path)
|
|
|
|
|
|
def delete(key):
|
|
"""删除指定缓存项;共享文件消失后其他 worker 会立即失效。"""
|
|
_cache.pop(key, None)
|
|
_file_signatures.pop(key, None)
|
|
try:
|
|
os.remove(_path(key))
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
|
|
def clear(key=None):
|
|
if key:
|
|
delete(key)
|
|
return
|
|
_cache.clear()
|
|
_file_signatures.clear()
|
|
try:
|
|
for filename in os.listdir(CACHE_DIR):
|
|
if filename.endswith('.cache'):
|
|
try:
|
|
os.remove(os.path.join(CACHE_DIR, filename))
|
|
except FileNotFoundError:
|
|
pass
|
|
except FileNotFoundError:
|
|
pass
|