Skip to content

Commit 8adf35a

Browse files
jpheinclaude
andcommitted
fix: add threading lock to graph cache, expand docstring
Address review feedback from @bensig: 1. Wrap cache reads/writes in threading.Lock for thread safety 2. Promote the col-arg caveat from inline comment to docstring Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1657a79 commit 8adf35a

1 file changed

Lines changed: 18 additions & 9 deletions

File tree

mempalace/palace_graph.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import hashlib
1919
import json
2020
import os
21+
import threading
2122
import time
2223
from collections import Counter, defaultdict
2324
from datetime import datetime, timezone
@@ -28,6 +29,7 @@
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
@@ -37,9 +39,10 @@
3739
def 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

4548
def _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

Comments
 (0)