mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-16 00:49:37 +00:00
fix(ingest): make memory-doc filenames unique so concurrent saves do not overwrite
save_query_result named files query_<second>_<slug50>.md and wrote them with write_text, so two saves in the same second whose questions share the first 50 characters resolved to one path and the later one silently replaced the earlier. Both calls returned normally. This is the common case when several agents sweep one subsystem in parallel. Add a short uuid to the name. The query_ prefix and .md suffix are unchanged so reflect.load_memory_docs and existing tests keep working. Fixes #3301
This commit is contained in:
1 parent
33362d9692
commit
0bdf89f64b
2 files changed
+18
-1
No files matched your search
+6
-1
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
from datetime import datetime, timezone
|
||||
@@ -299,7 +300,11 @@ def save_query_result(
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
slug = re.sub(r"[^\w]", "_", question.lower())[:50].strip("_")
|
||||
filename = f"query_{now.strftime('%Y%m%d_%H%M%S')}_{slug}.md"
|
||||
# A second-granularity stamp plus a 50-char slug is not unique: two saves in
|
||||
# the same second whose questions share a prefix resolve to one path, and the
|
||||
# later write_text silently replaces the earlier one (#3301). The short uuid
|
||||
# makes every save its own file; the query_ prefix and .md suffix are kept.
|
||||
filename = f"query_{now.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}_{slug}.md"
|
||||
|
||||
frontmatter_lines = [
|
||||
"---",
|
||||
|
||||
@@ -98,3 +98,15 @@ def test_no_outcome_means_no_outcome_section(tmp_path):
|
||||
def test_invalid_outcome_rejected(tmp_path):
|
||||
with pytest.raises(ValueError):
|
||||
save_query_result("q", "a", tmp_path / "memory", outcome="great")
|
||||
|
||||
|
||||
def test_concurrent_saves_of_the_same_question_do_not_overwrite(tmp_path):
|
||||
"""Regression for #3301: a second-granularity stamp plus a 50-char slug is
|
||||
not unique, so saves in the same second sharing a prefix collapsed into one
|
||||
file and the earlier ones were silently lost."""
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
mem = tmp_path / "memory"
|
||||
with ThreadPoolExecutor(max_workers=20) as ex:
|
||||
paths = list(ex.map(lambda _: save_query_result("how does auth work", "a", mem), range(20)))
|
||||
assert len({p.name for p in paths}) == 20
|
||||
assert len(list(mem.glob("*.md"))) == 20
|
||||
Reference in new issue
Block a user