"""简单的跨进程文件缓存。 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