feat: add invalidate_all_caches() and GET /__hot_reload__ endpoint
invalidate_all_caches() clears module-level caches: - rbac: UserPermissions.ur_caches + rp_caches - pricing: PricingProgram.pricing_data - uapi: UAPIData.apidata + org_users - llmage: _uapi_cache + _uapiio_cache GET /__hot_reload__ endpoint (registered only when hot_reload enabled): - Manual trigger for cache flush during development - Returns JSON with list of cleared caches Automatic trigger: - config.json change → invalidate_all_caches() called automatically Each module cache is cleared independently with try/except so one module's import failure doesn't block others.
This commit is contained in:
parent
31d66aa91b
commit
cd578de80d
Binary file not shown.
@ -22,7 +22,7 @@ from .serverenv import ServerEnv
|
||||
from .filestorage import TmpFileRecord
|
||||
from .loadplugins import load_plugins
|
||||
from .real_ip import real_ip_middleware
|
||||
from .hotreload import HotReloader, get_i18n_paths, hot_reload_task
|
||||
from .hotreload import HotReloader, get_i18n_paths, hot_reload_task, hot_reload_handler, invalidate_all_caches
|
||||
|
||||
startup_coros = []
|
||||
cleanupctx_coros = []
|
||||
@ -135,6 +135,8 @@ class ConfiguredServer:
|
||||
i18n_paths = get_i18n_paths(workdir)
|
||||
reloader = HotReloader(config_path, i18n_paths)
|
||||
reloader.set_interval(interval)
|
||||
# Register HTTP endpoint for manual cache flush
|
||||
self.app.router.add_get('/__hot_reload__', hot_reload_handler)
|
||||
async def _hot_reload_startup(app):
|
||||
task = asyncio.create_task(hot_reload_task(app, reloader))
|
||||
app['hot_reload_task'] = task
|
||||
@ -148,7 +150,7 @@ class ConfiguredServer:
|
||||
pass
|
||||
self.app.on_startup.append(_hot_reload_startup)
|
||||
self.app.cleanup_ctx.append(_hot_reload_cleanup)
|
||||
print(f'hot_reload enabled, interval={interval}s, pid will check independently')
|
||||
print(f'hot_reload enabled, interval={interval}s, endpoint=GET /__hot_reload__')
|
||||
|
||||
web.run_app(self.build_app(),host=config.website.host or '0.0.0.0',
|
||||
port=port,
|
||||
|
||||
@ -6,6 +6,10 @@ Watches file mtimes and triggers reload of cached resources:
|
||||
- i18n files (MiniI18N singleton)
|
||||
- Jinja2 template cache (auto_reload already handles this)
|
||||
|
||||
Module caches (rbac/pricing/uapi/llmage) can be cleared via:
|
||||
- HTTP endpoint: GET /__hot_reload__
|
||||
- Automatic when config.json changes
|
||||
|
||||
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.
|
||||
@ -112,12 +116,16 @@ class HotReloader:
|
||||
return reloaded
|
||||
|
||||
def _reload_config(self):
|
||||
"""Clear JsonConfig singleton so next getConfig() call reloads from disk."""
|
||||
"""Clear JsonConfig singleton so next getConfig() call reloads from disk.
|
||||
Also clear all module caches since config changes may affect them.
|
||||
"""
|
||||
try:
|
||||
from appPublic.jsonConfig import JsonConfig
|
||||
# SingletonDecorator stores instance as .instance
|
||||
JsonConfig.instance = None
|
||||
info('[hot_reload] config.json changed, cache cleared')
|
||||
# Also clear module caches since config may affect module_cache settings
|
||||
invalidate_all_caches()
|
||||
except Exception as e:
|
||||
warning(f'[hot_reload] failed to reload config: {e}')
|
||||
|
||||
@ -168,3 +176,73 @@ async def hot_reload_task(app, reloader):
|
||||
except asyncio.CancelledError:
|
||||
info('[hot_reload] stopped')
|
||||
raise
|
||||
|
||||
|
||||
def invalidate_all_caches():
|
||||
"""Clear all module caches (rbac/pricing/uapi/llmage).
|
||||
|
||||
Called automatically when config.json changes, or manually via
|
||||
GET /__hot_reload__ endpoint.
|
||||
|
||||
Each module cache is cleared independently with try/except to
|
||||
prevent one module's failure from blocking others.
|
||||
"""
|
||||
cleared = []
|
||||
|
||||
# rbac: UserPermissions singleton with LRU caches
|
||||
try:
|
||||
from rbac.userperm import UserPermissions
|
||||
up = UserPermissions()
|
||||
up.ur_caches.clear()
|
||||
up.invalidate_rp_cache()
|
||||
cleared.append('rbac')
|
||||
except Exception as e:
|
||||
debug(f'[hot_reload] rbac cache clear skipped: {e}')
|
||||
|
||||
# pricing: PricingProgram class-level pricing_data dict
|
||||
try:
|
||||
from pricing.pricing import PricingProgram
|
||||
PricingProgram.pricing_data.clear()
|
||||
cleared.append('pricing')
|
||||
except Exception as e:
|
||||
debug(f'[hot_reload] pricing cache clear skipped: {e}')
|
||||
|
||||
# uapi: UAPIData singleton with apidata and org_users dicts
|
||||
try:
|
||||
from uapi.apidata import UAPIData
|
||||
ud = UAPIData()
|
||||
ud.apidata.clear()
|
||||
ud.org_users.clear()
|
||||
cleared.append('uapi')
|
||||
except Exception as e:
|
||||
debug(f'[hot_reload] uapi cache clear skipped: {e}')
|
||||
|
||||
# llmage: module-level _uapi_cache and _uapiio_cache
|
||||
try:
|
||||
from llmage.utils import invalidate_uapi_cache
|
||||
invalidate_uapi_cache() # clears both _uapi_cache and _uapiio_cache
|
||||
cleared.append('llmage')
|
||||
except Exception as e:
|
||||
debug(f'[hot_reload] llmage cache clear skipped: {e}')
|
||||
|
||||
if cleared:
|
||||
info(f'[hot_reload] cleared caches: {cleared}')
|
||||
return cleared
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Returns JSON with list of cleared caches.
|
||||
"""
|
||||
from aiohttp import web
|
||||
cleared = invalidate_all_caches()
|
||||
return web.json_response({
|
||||
'status': 'ok',
|
||||
'cleared': cleared,
|
||||
'timestamp': time.time()
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user