Skip to content

Commit 46ac61e

Browse files
committed
Fix: Propagate thread-safety locks to memoryos-mcp and memoryos-playground
Commit 97453e4 added threading.Lock() to ShortTermMemory, MidTermMemory and LongTermMemory in memoryos-pypi to prevent race conditions on JSON writes, but the same fix was not applied to the memoryos-mcp and memoryos-playground variants, which contain near-identical copies of these classes. Both variants are exposed to concurrent access (the MCP server serves parallel tool calls; the Flask playground handles concurrent HTTP requests), so the race condition still causes data corruption, lost entries, or truncated files there. This commit mirrors the pypi fix in both variants: - Add `import threading` at module top - Initialize `self.lock = threading.Lock()` in __init__ - Wrap the json.dump call in save() with `with self.lock:` Files: - memoryos-mcp/memoryos/short_term.py - memoryos-mcp/memoryos/mid_term.py - memoryos-mcp/memoryos/long_term.py - memoryos-playground/short_term.py - memoryos-playground/mid_term.py - memoryos-playground/long_term.py
1 parent 1d71706 commit 46ac61e

6 files changed

Lines changed: 30 additions & 12 deletions

File tree

memoryos-mcp/memoryos/long_term.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import numpy as np
33
import faiss
44
from collections import deque
5+
import threading
56
try:
67
from .utils import get_timestamp, get_embedding, normalize_vector, ensure_directory_exists
78
except ImportError:
@@ -19,6 +20,7 @@ def __init__(self, file_path, knowledge_capacity=100, embedding_model_name: str
1920

2021
self.embedding_model_name = embedding_model_name
2122
self.embedding_model_kwargs = embedding_model_kwargs if embedding_model_kwargs is not None else {}
23+
self.lock = threading.Lock()
2224
self.load()
2325

2426
def update_user_profile(self, user_id, new_data, merge=True):
@@ -144,8 +146,9 @@ def save(self):
144146
"assistant_knowledge": list(self.assistant_knowledge)
145147
}
146148
try:
147-
with open(self.file_path, "w", encoding="utf-8") as f:
148-
json.dump(data, f, ensure_ascii=False, indent=2)
149+
with self.lock:
150+
with open(self.file_path, "w", encoding="utf-8") as f:
151+
json.dump(data, f, ensure_ascii=False, indent=2)
149152
except IOError as e:
150153
print(f"Error saving LongTermMemory to {self.file_path}: {e}")
151154

memoryos-mcp/memoryos/mid_term.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections import defaultdict
44
import faiss
55
import heapq
6+
import threading
67
from datetime import datetime
78

89
try:
@@ -46,6 +47,7 @@ def __init__(self, file_path: str, client: OpenAIClient, max_capacity=2000, embe
4647

4748
self.embedding_model_name = embedding_model_name
4849
self.embedding_model_kwargs = embedding_model_kwargs if embedding_model_kwargs is not None else {}
50+
self.lock = threading.Lock()
4951
self.load()
5052

5153
def get_page_by_id(self, page_id):
@@ -370,8 +372,9 @@ def save(self):
370372
# "heap_snapshot": self.heap
371373
}
372374
try:
373-
with open(self.file_path, "w", encoding="utf-8") as f:
374-
json.dump(data_to_save, f, ensure_ascii=False, indent=2)
375+
with self.lock:
376+
with open(self.file_path, "w", encoding="utf-8") as f:
377+
json.dump(data_to_save, f, ensure_ascii=False, indent=2)
375378
except IOError as e:
376379
print(f"Error saving MidTermMemory to {self.file_path}: {e}")
377380

memoryos-mcp/memoryos/short_term.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from collections import deque
3+
import threading
34
try:
45
from .utils import get_timestamp, ensure_directory_exists
56
except ImportError:
@@ -11,6 +12,7 @@ def __init__(self, file_path, max_capacity=10):
1112
self.file_path = file_path
1213
ensure_directory_exists(self.file_path)
1314
self.memory = deque(maxlen=max_capacity)
15+
self.lock = threading.Lock()
1416
self.load()
1517

1618
def add_qa_pair(self, qa_pair):
@@ -38,8 +40,9 @@ def pop_oldest(self):
3840

3941
def save(self):
4042
try:
41-
with open(self.file_path, "w", encoding="utf-8") as f:
42-
json.dump(list(self.memory), f, ensure_ascii=False, indent=2)
43+
with self.lock:
44+
with open(self.file_path, "w", encoding="utf-8") as f:
45+
json.dump(list(self.memory), f, ensure_ascii=False, indent=2)
4346
except IOError as e:
4447
print(f"Error saving ShortTermMemory to {self.file_path}: {e}")
4548

memoryos-playground/long_term.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import numpy as np
33
import faiss
44
from collections import deque
5+
import threading
56
try:
67
from .utils import get_timestamp, get_embedding, normalize_vector, ensure_directory_exists
78
except ImportError:
@@ -19,6 +20,7 @@ def __init__(self, file_path, knowledge_capacity=100, embedding_model_name: str
1920

2021
self.embedding_model_name = embedding_model_name
2122
self.embedding_model_kwargs = embedding_model_kwargs if embedding_model_kwargs is not None else {}
23+
self.lock = threading.Lock()
2224
self.load()
2325

2426
def update_user_profile(self, user_id, new_data, merge=True):
@@ -144,8 +146,9 @@ def save(self):
144146
"assistant_knowledge": list(self.assistant_knowledge)
145147
}
146148
try:
147-
with open(self.file_path, "w", encoding="utf-8") as f:
148-
json.dump(data, f, ensure_ascii=False, indent=2)
149+
with self.lock:
150+
with open(self.file_path, "w", encoding="utf-8") as f:
151+
json.dump(data, f, ensure_ascii=False, indent=2)
149152
except IOError as e:
150153
print(f"Error saving LongTermMemory to {self.file_path}: {e}")
151154

memoryos-playground/mid_term.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections import defaultdict
44
import faiss
55
import heapq
6+
import threading
67
from datetime import datetime
78

89
try:
@@ -46,6 +47,7 @@ def __init__(self, file_path: str, client: OpenAIClient, max_capacity=2000, embe
4647

4748
self.embedding_model_name = embedding_model_name
4849
self.embedding_model_kwargs = embedding_model_kwargs if embedding_model_kwargs is not None else {}
50+
self.lock = threading.Lock()
4951
self.load()
5052

5153
def get_page_by_id(self, page_id):
@@ -370,8 +372,9 @@ def save(self):
370372
# "heap_snapshot": self.heap
371373
}
372374
try:
373-
with open(self.file_path, "w", encoding="utf-8") as f:
374-
json.dump(data_to_save, f, ensure_ascii=False, indent=2)
375+
with self.lock:
376+
with open(self.file_path, "w", encoding="utf-8") as f:
377+
json.dump(data_to_save, f, ensure_ascii=False, indent=2)
375378
except IOError as e:
376379
print(f"Error saving MidTermMemory to {self.file_path}: {e}")
377380

memoryos-playground/short_term.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from collections import deque
3+
import threading
34
try:
45
from .utils import get_timestamp, ensure_directory_exists
56
except ImportError:
@@ -11,6 +12,7 @@ def __init__(self, file_path, max_capacity=10):
1112
self.file_path = file_path
1213
ensure_directory_exists(self.file_path)
1314
self.memory = deque(maxlen=max_capacity)
15+
self.lock = threading.Lock()
1416
self.load()
1517

1618
def add_qa_pair(self, qa_pair):
@@ -38,8 +40,9 @@ def pop_oldest(self):
3840

3941
def save(self):
4042
try:
41-
with open(self.file_path, "w", encoding="utf-8") as f:
42-
json.dump(list(self.memory), f, ensure_ascii=False, indent=2)
43+
with self.lock:
44+
with open(self.file_path, "w", encoding="utf-8") as f:
45+
json.dump(list(self.memory), f, ensure_ascii=False, indent=2)
4346
except IOError as e:
4447
print(f"Error saving ShortTermMemory to {self.file_path}: {e}")
4548

0 commit comments

Comments
 (0)