perf(webui/session-load-latency) tier3c: fix scene comparison direction in cache fast-path

The fast-path scene comparison in _cached_session_lags_disk had two bugs
flagged by nesquena-hermes review:

1. Direction: used symmetric != instead of directional subset check.
   When the cache had scene keys that disk lacked (cache-ahead), the old
   code force-reloaded from disk, silently dropping un-persisted scene
   data. Now mirrors master: reload only when disk is strictly ahead
   (disk_keys.issubset(cached_keys)). The 'not disk_scenes' early-return
   is also removed — empty disk scenes means disk is not ahead, so keep
   the cache.

2. Key filtering mismatch: used raw set(disk_scenes.keys()) instead of
   _anchor_scene_record_keys() helper which filters to truthy keys with
   dict values. Now filters disk keys the same way as master, and uses
   _anchor_scene_record_keys(cached) for the cached session object.

Also updated the tier3b content-prefix docstring to clarify it is a
best-effort backstop limited to the first 128 chars, not a full
integrity check.
This commit is contained in:
Konstantin M committed 2026-07-09 02:21:25 +00:00
1 parent aa78590164
commit b5c0447a4a
2 files changed
+34 -19

No files matched your search

+27 -19
View File
@@ -3133,25 +3133,33 @@ def _cached_session_lags_disk(cached) -> bool:
disk_scenes = disk_meta_quick.get('anchor_activity_scenes') or {} disk_scenes = disk_meta_quick.get('anchor_activity_scenes') or {}
if not isinstance(disk_scenes, dict): if not isinstance(disk_scenes, dict):
disk_scenes = {} disk_scenes = {}
if not disk_scenes or set(disk_scenes.keys()) != set(cached_scenes.keys()): if disk_scenes:
# Scene key set differs — disk has been updated with new # Directional: only reload when disk is strictly ahead
# keys the cache hasn't seen. # of cache. Mirror master's subset comparison — cache
return True # that is ahead of disk must NOT force a reload, or
# Same key set: check the latest updated_at timestamp. # un-persisted scene data is silently dropped.
def _max_updated(records): disk_keys = {str(key) for key, value in disk_scenes.items()
latest = 0.0 if key and isinstance(value, dict)}
for record in records.values(): cached_keys = _anchor_scene_record_keys(cached)
if not isinstance(record, dict): if disk_keys and not disk_keys.issubset(cached_keys):
continue return True
try: # Same key set (or disk is subset): check the latest
ua = float(record.get('updated_at') or 0) # updated_at timestamp.
except (TypeError, ValueError): def _max_updated(records):
ua = 0.0 latest = 0.0
if ua > latest: for record in records.values():
latest = ua if not isinstance(record, dict):
return latest continue
if _max_updated(disk_scenes) > _max_updated(cached_scenes): try:
return True ua = float(record.get('updated_at') or 0)
except (TypeError, ValueError):
ua = 0.0
if ua > latest:
latest = ua
return latest
if _max_updated(disk_scenes) > _anchor_scene_records_updated_at(cached):
return True
# disk has no scenes, cache does -> cache is ahead; keep it.
else: else:
# Cached session has no scene records. Check if disk has gained # Cached session has no scene records. Check if disk has gained
# the first scene record — without this the fast-path would miss # the first scene record — without this the fast-path would miss
+7
View File
@@ -6333,6 +6333,13 @@ def _read_profile_config_cached(profile_name: str, cfg_path: str) -> dict | None
in-place rewrites where inode+mtime+size are identical on coarse in-place rewrites where inode+mtime+size are identical on coarse
filesystems — most editors use atomic-rename (new inode), but tools filesystems — most editors use atomic-rename (new inode), but tools
like sed -i or file.write() in-place can keep the same inode. like sed -i or file.write() in-place can keep the same inode.
NOTE: This is a best-effort backstop, not a full integrity check.
Edits that change bytes only after the first 128 characters while
keeping inode+mtime+size identical are not detected until the TTL
expires. This trade-off is acceptable because the TTL is only 60s
and the common case (atomic-rename editors) is fully handled by
the inode key component.
""" """
try: try:
st = os.stat(cfg_path) st = os.stat(cfg_path)