feat: cross-process cache invalidation via signal file for reuse_port multi-process
This commit is contained in:
parent
cd578de80d
commit
42eff6cda0
@ -7,9 +7,15 @@ Watches file mtimes and triggers reload of cached resources:
|
||||
- Jinja2 template cache (auto_reload already handles this)
|
||||
|
||||
Module caches (rbac/pricing/uapi/llmage) can be cleared via:
|
||||
- HTTP endpoint: GET /__hot_reload__
|
||||
- HTTP endpoint: GET /__hot_reload__ (triggers all workers via signal file)
|
||||
- Automatic when config.json changes
|
||||
|
||||
Cross-process cache invalidation (reuse_port multi-process):
|
||||
- GET /__hot_reload__ writes to /tmp/.sage_cache_invalidate signal file
|
||||
- All workers detect signal file mtime change within their check interval
|
||||
- Each worker independently clears its own caches
|
||||
- No cross-process IPC or Redis needed
|
||||
|
||||
Multi-process safe: each process independently checks mtimes via stat(),
|
||||
no cross-process coordination needed. When a file changes on disk,
|
||||
all processes detect it on their next check cycle.
|
||||
@ -64,6 +70,9 @@ class FileWatcher:
|
||||
return changed
|
||||
|
||||
|
||||
SIGNAL_FILE = '/tmp/.sage_cache_invalidate'
|
||||
|
||||
|
||||
class HotReloader:
|
||||
"""Hot-reload cached resources when source files change.
|
||||
|
||||
@ -78,6 +87,11 @@ class HotReloader:
|
||||
self._i18n_paths = i18n_paths or []
|
||||
self._last_check = 0
|
||||
self._interval = 2 # seconds between checks
|
||||
# Record current signal file mtime to avoid false trigger on startup
|
||||
try:
|
||||
self._last_signal_mtime = os.path.getmtime(SIGNAL_FILE)
|
||||
except OSError:
|
||||
self._last_signal_mtime = 0
|
||||
|
||||
def set_interval(self, interval):
|
||||
self._interval = interval
|
||||
@ -89,11 +103,22 @@ class HotReloader:
|
||||
self._last_check = now
|
||||
return True
|
||||
|
||||
def _check_signal_file(self):
|
||||
"""Check if cache invalidation signal file was updated."""
|
||||
try:
|
||||
mtime = os.path.getmtime(SIGNAL_FILE)
|
||||
if mtime > self._last_signal_mtime:
|
||||
self._last_signal_mtime = mtime
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def check_and_reload(self):
|
||||
"""Check for file changes and reload if needed.
|
||||
|
||||
Returns:
|
||||
dict with keys 'config', 'i18n' indicating what was reloaded
|
||||
dict with keys 'config', 'i18n', 'signal' indicating what was reloaded
|
||||
"""
|
||||
if not self._should_check():
|
||||
return {}
|
||||
@ -113,6 +138,11 @@ class HotReloader:
|
||||
self._reload_i18n()
|
||||
reloaded['i18n'] = True
|
||||
|
||||
# Check signal file (cross-process cache invalidation)
|
||||
if self._check_signal_file():
|
||||
invalidate_all_caches()
|
||||
reloaded['signal'] = True
|
||||
|
||||
return reloaded
|
||||
|
||||
def _reload_config(self):
|
||||
@ -233,16 +263,25 @@ def invalidate_all_caches():
|
||||
async def hot_reload_handler(request):
|
||||
"""HTTP endpoint handler for GET /__hot_reload__.
|
||||
|
||||
Manually triggers cache invalidation. Useful for development
|
||||
when database changes aren't detected automatically.
|
||||
Triggers cache invalidation across all workers via signal file.
|
||||
Each worker detects the signal file change within its check interval
|
||||
and clears its own caches.
|
||||
|
||||
Returns JSON with list of cleared caches.
|
||||
Returns JSON with confirmation that signal was sent.
|
||||
"""
|
||||
from aiohttp import web
|
||||
|
||||
# Write signal file - all workers will detect this
|
||||
with open(SIGNAL_FILE, 'w') as f:
|
||||
f.write(str(time.time()))
|
||||
|
||||
# Also clear current worker's cache immediately
|
||||
cleared = invalidate_all_caches()
|
||||
|
||||
return web.json_response({
|
||||
'status': 'ok',
|
||||
'cleared': cleared,
|
||||
'message': 'Signal sent to all workers',
|
||||
'timestamp': time.time()
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user