From 8bb2d85d8c9ee82860e8baaa09a7e6e24978d63b Mon Sep 17 00:00:00 2001 From: Belugary <53219544+Belugary@users.noreply.github.com> Date: Wed, 13 May 2026 13:25:00 +0800 Subject: [PATCH] fix(contact): auto-invalidate in-memory caches when contact.db is re-decrypted (#98) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem `_contact_names`, `_contact_full`, `_contact_tags`, and `_self_username` are populated lazily on first access and never invalidated for the process lifetime. When `contact.db` is re-decrypted (new contact added, remark or group name edited, etc.) the on-disk DB updates but the running MCP server keeps serving stale data — newly-added contacts are invisible to `resolve_username` and downstream tools until the server is restarted. ## Fix Track the mtime of the contact.db backing file. On every `_get_contact_db_path()` call (which all contact accessors go through), compare against `_contact_db_mtime`; if it changed, clear all four caches and record the new mtime. Lookups that don't trigger a real re-decryption pay only one `os.path.getmtime()` syscall. The function is reorganized so `_get_contact_db_path()` is the single source of truth for both "where is contact.db" and "do we need to invalidate" — `get_contact_names` and `_load_contact_tags` consult it unconditionally before the early-return on the populated cache. Also reorders `_get_self_username` to call `get_contact_names()` first (which now triggers the mtime check via the path lookup) before returning a cached `_self_username` — otherwise the rename case would still resolve to the stale name. ## Tests Baseline 183 → 183 passing, 0 regressions. The pattern (mtime-track + invalidate-on-change) mirrors the existing behaviour of DBCache, which already re-decrypts contact.db when the source mtime changes; this fix closes the symmetric gap on the in-memory side. ## Scope - `mcp_server.py` only. - No public surface change. Affects the contact-cache layer's behaviour on re-decryption — previously: stale until restart; now: refreshed on next contact-related call. --- mcp_server.py | 90 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/mcp_server.py b/mcp_server.py index 1077baa..35e93a3 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -225,6 +225,15 @@ _contact_names = None # {username: display_name} _contact_full = None # [{username, nick_name, remark}] _contact_tags = None # {label_id: {name, sort_order, members: [{username, display_name}]}} _self_username = None +_contact_db_mtime = 0 # mtime of the decrypted contact.db when caches were last populated + + +def _invalidate_contact_caches(): + global _contact_names, _contact_full, _contact_tags, _self_username + _contact_names = None + _contact_full = None + _contact_tags = None + _self_username = None _XML_UNSAFE_RE = re.compile(r'