What happened?
Three read methods in KnowledgeGraph (query_relationship, timeline, stats) access the shared sqlite3.Connection object without acquiring self._lock. All write methods (add_entity, add_triple, invalidate) and the query_entity read method correctly hold self._lock before touching the connection, but these three were missed.
When the MCP server handles concurrent tool calls (e.g. mempalace_kg_timeline and mempalace_kg_add running at the same time), a write can interleave mid-read on the same connection object, which can produce:
- Garbled / inconsistent results (most silent and dangerous)
- A
ProgrammingError: recursive use of cursor crash on some SQLite builds
Note: sqlite3.connect(..., check_same_thread=False) silences the thread-origin check but does not make the connection thread-safe — a Python-level lock is still required.
What did you expect?
All methods that access the shared sqlite3.Connection in KnowledgeGraph should hold self._lock for the full duration of their database operation, matching the pattern already used by add_entity, add_triple, invalidate, and query_entity.
How to reproduce:
# Code-level proof — shows which methods are missing the lock
import inspect
from mempalace.knowledge_graph import KnowledgeGraph
for method in ["add_triple", "query_entity", "query_relationship", "timeline", "stats"]:
src = inspect.getsource(getattr(KnowledgeGraph, method))
has = "self._lock" in src
print(f"{method:<25} {'✓ locked' if has else '✗ MISSING LOCK'}")
Expected output (buggy):
add_triple ✓ locked
query_entity ✓ locked
query_relationship ✗ MISSING LOCK
timeline ✗ MISSING LOCK
stats ✗ MISSING LOCK
Fix:
Wrap each of the three methods in with self._lock: and move conn = self._conn() inside the block, exactly as query_entity does.
Environment:
- OS: OS: Linux (Ubuntu 24.04)
- Python version: 3.12
- MemPal version: 3.3.0 (current
develop branch, commit 4de9e13)
What happened?
Three read methods in
KnowledgeGraph(query_relationship,timeline,stats) access the sharedsqlite3.Connectionobject without acquiringself._lock. All write methods (add_entity,add_triple,invalidate) and thequery_entityread method correctly holdself._lockbefore touching the connection, but these three were missed.When the MCP server handles concurrent tool calls (e.g.
mempalace_kg_timelineandmempalace_kg_addrunning at the same time), a write can interleave mid-read on the same connection object, which can produce:ProgrammingError: recursive use of cursorcrash on some SQLite buildsNote:
sqlite3.connect(..., check_same_thread=False)silences the thread-origin check but does not make the connection thread-safe — a Python-level lock is still required.What did you expect?
All methods that access the shared
sqlite3.ConnectioninKnowledgeGraphshould holdself._lockfor the full duration of their database operation, matching the pattern already used byadd_entity,add_triple,invalidate, andquery_entity.How to reproduce:
Expected output (buggy):
Fix:
Wrap each of the three methods in
with self._lock:and moveconn = self._conn()inside the block, exactly asquery_entitydoes.Environment:
developbranch, commit4de9e13)