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:
@@ -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":
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user