MemPalace/mempalace · error · BackendClosedError
SQLiteExactCollection has been closed
Error message
SQLiteExactCollection has been closed
What it means
Raised by `SQLiteExactCollection._ensure_open`, which guards every collection operation. It fires when the collection object was explicitly closed, or when the underlying shared handle was closed (e.g. `backend.close()` or palace close reaped the connection out of the registry). This is a use-after-close guard: all later reads/writes on that collection object fail fast instead of touching a dead sqlite connection.
Source
Thrown at mempalace/backends/sqlite_exact.py:280
self.lock = lock
self.palace_path = palace_path
self.read_only = read_only
# True when opened with ``immutable=1`` because no WAL existed at connect
# time. A later writer can create WAL sidecars that this connection will
# never see, so the backend must reopen once those files appear.
self.immutable = immutable
self.closed = False
class SQLiteExactCollection(BaseCollection):
def __init__(self, handle: _SQLiteExactHandle, collection_name: str):
self._handle = handle
self._collection_name = collection_name
self._closed = False
def _ensure_open(self) -> None:
if self._closed or self._handle.closed:
raise BackendClosedError("SQLiteExactCollection has been closed")
@contextlib.contextmanager
def _write_lock(self):
"""Serialize this handle before taking process-wide writer ownership.
``mine_palace_lock`` grants cross-thread re-entrant access whenever
this process already owns the palace. Taking it before ``handle.lock``
lets a waiting thread consume that re-entrant credit, outlive the
thread that owns the OS lease, and then mutate after the lease has been
released. The handle mutex must therefore be the outer context.
"""
# Late import avoids a palace.py -> backend -> palace.py cycle.
from ..palace import mine_palace_lock
with self._handle.lock:
self._ensure_open()
with mine_palace_lock(self._handle.palace_path):
yieldView on GitHub (pinned to 06cb6987f0)
Solutions
- Re-fetch the collection after reopening: `backend = SQLiteExactBackend(); col = backend.get_collection(...)` — do not cache collections across close cycles.
- Scope collection usage inside a `with`/try block that owns the backend lifetime, and close only after all work (including background threads) is joined.
- In multi-threaded code, signal background threads to stop before calling `backend.close()`.
- Catch BackendClosedError at the top level of hooks to treat late work as a no-op and log it.
Example fix
# before backend.close() col.count() # BackendClosedError # after backend.close() backend = SQLiteExactBackend() col = backend.get_collection(palace, "drawers") col.count()
Defensive patterns
Strategy: try-catch
Validate before calling
def collection_usable(col) -> bool:
return not getattr(col, "_closed", True) and not getattr(getattr(col, "_handle", None), "closed", True) Try / catch
from mempalace.backends.base import BackendClosedError
try:
col.count()
except BackendClosedError:
backend = SQLiteExactBackend()
col = backend.get_collection(palace, name) # reacquire after reopen
col.count() Prevention
- Scope collection usage to the backend's lifetime; close backends only after joining background threads.
- Never cache collection objects across module reloads or shutdown hooks.
- In hook scripts, wrap the whole memory operation and treat BackendClosedError as a clean no-op.
When it happens
Trigger: Calling any method on a SQLiteExactCollection after `col.close()`; calling after `backend.close()` closed the shared handle; holding a collection reference across a palace repair/reopen cycle; using a collection in a background hook thread after the main thread shut the backend down.
Common situations: Long-lived module-level collection handles in a server or hook process that gets torn down; test fixtures that close the backend in teardown while an assertion afterwards still touches the collection; a shutdown race where a save hook runs during interpreter exit after close.
Related errors
- MilvusCollection has been closed
- PgVectorBackend has been closed
- QdrantCollection has been closed
- operator {key!r} not supported by sqlite_exact
- {collection_name}
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/021860a1f8df1ad4.
Report an issue: GitHub.