fix(memory): keep a stale holder's late close() from evicting a fresh registry entry

Follow-up to the #88347 salvage: after release_all_under() force-closes a
profile's shared connection, a store re-created on the same path registers
a fresh entry under the same key. A stale holder that later calls close()
would pop that fresh entry (its refs were transferred nowhere), letting a
third store open a second connection to the same database — exactly the
multi-writer contention the shared registry exists to prevent. close()
now evicts the registry entry only when it is still its own.
This commit is contained in:
Teknium
2026-08-17 15:56:32 -07:00
parent 4f354c27b7
commit aa8ceed4b6
2 changed files with 45 additions and 1 deletions

View File

@@ -671,7 +671,14 @@ class MemoryStore:
try:
entry["conn"].close()
finally:
MemoryStore._shared.pop(self._key, None)
# Pop only OUR entry. After release_all_under() force-
# closed this entry (profile delete, #88347) a same-path
# store may have re-registered a FRESH entry under the
# same key; a stale holder's late close() must not evict
# it — that would silently reintroduce the multi-writer
# contention this registry exists to prevent.
if MemoryStore._shared.get(self._key) is entry:
MemoryStore._shared.pop(self._key, None)
self._entry = None
def __enter__(self) -> "MemoryStore":

View File

@@ -91,3 +91,40 @@ def test_release_all_under_closes_connections_inside_directory_only(tmp_path):
outside.close()
def test_stale_holder_close_does_not_evict_fresh_registry_entry(tmp_path):
"""Follow-up to #88347 — a stale holder's late ``close()`` must be inert.
After ``release_all_under`` force-closes a profile's connection, a store
re-created on the same path registers a FRESH shared entry under the same
key. If the stale holder (whose entry was force-closed) then calls
``close()``, it must not pop the fresh entry out of the registry — that
would let a third store open a SECOND connection to the same database and
silently reintroduce the multi-writer contention the registry prevents.
"""
from plugins.memory.holographic.store import MemoryStore
profile_dir = tmp_path / "profiles" / "default-2"
profile_dir.mkdir(parents=True)
db_path = profile_dir / "memory_store.db"
stale = MemoryStore(db_path=db_path, hrr_dim=64)
assert MemoryStore.release_all_under(profile_dir) == 1
fresh = MemoryStore(db_path=db_path, hrr_dim=64)
key = fresh._key
fresh_entry = MemoryStore._shared[key]
# The stale holder's late close must leave the fresh entry registered...
stale.close()
assert MemoryStore._shared.get(key) is fresh_entry
# ...and a third store must attach to the SAME shared connection.
third = MemoryStore(db_path=db_path, hrr_dim=64)
try:
assert third._conn is fresh._conn
finally:
third.close()
fresh.close()
# Normal last-holder close still evicts its own entry.
assert key not in MemoryStore._shared