1818import hashlib
1919import json
2020import os
21+ import threading
2122import time
2223from collections import Counter , defaultdict
2324from datetime import datetime , timezone
2829
2930# Module-level graph cache with TTL and write-invalidation.
3031# Warm cache serves build_graph() in O(1); invalidate_graph_cache() clears on writes.
32+ _graph_cache_lock = threading .Lock ()
3133_graph_cache_nodes = None
3234_graph_cache_edges = None
3335_graph_cache_time = 0.0
3739def invalidate_graph_cache ():
3840 """Clear the graph cache. Called from mcp_server.py on writes."""
3941 global _graph_cache_nodes , _graph_cache_edges , _graph_cache_time
40- _graph_cache_nodes = None
41- _graph_cache_edges = None
42- _graph_cache_time = 0.0
42+ with _graph_cache_lock :
43+ _graph_cache_nodes = None
44+ _graph_cache_edges = None
45+ _graph_cache_time = 0.0
4346
4447
4548def _get_collection (config = None ):
@@ -59,7 +62,11 @@ def build_graph(col=None, config=None):
5962 Build the palace graph from ChromaDB metadata.
6063
6164 Returns cached result if fresh (within TTL). Cache is invalidated
62- on writes via invalidate_graph_cache().
65+ on writes via invalidate_graph_cache(). Thread-safe via _graph_cache_lock.
66+
67+ Note: warm cache ignores ``col`` and ``config`` arguments — this is
68+ intentional for the MCP server's single-palace use case. Callers
69+ switching collections should call ``invalidate_graph_cache()`` first.
6370
6471 Returns:
6572 nodes: dict of {room: {wings: set, halls: set, count: int}}
@@ -69,8 +76,9 @@ def build_graph(col=None, config=None):
6976 now = time .time ()
7077 # NOTE: warm cache ignores col/config args — intentional for the MCP server's
7178 # single-palace use case. Callers switching collections must invalidate first.
72- if _graph_cache_nodes is not None and (now - _graph_cache_time ) < _GRAPH_CACHE_TTL :
73- return _graph_cache_nodes , _graph_cache_edges
79+ with _graph_cache_lock :
80+ if _graph_cache_nodes is not None and (now - _graph_cache_time ) < _GRAPH_CACHE_TTL :
81+ return _graph_cache_nodes , _graph_cache_edges
7482
7583 if col is None :
7684 col = _get_collection (config )
@@ -130,9 +138,10 @@ def build_graph(col=None, config=None):
130138 # Only cache non-empty graphs so new data is picked up immediately
131139 # when the palace is first populated.
132140 if nodes :
133- _graph_cache_nodes = nodes
134- _graph_cache_edges = edges
135- _graph_cache_time = time .time ()
141+ with _graph_cache_lock :
142+ _graph_cache_nodes = nodes
143+ _graph_cache_edges = edges
144+ _graph_cache_time = time .time ()
136145
137146 return nodes , edges
138147
0 commit comments