MemPalace/mempalace · error · BackendClosedError
QdrantCollection has been closed
Error message
QdrantCollection has been closed
What it means
Raised by QdrantCollection._ensure_open() when an operation is attempted on a collection handle after close() was called on it (or on its parent QdrantBackend). It is a BackendClosedError (BackendError subclass) and marks a use-after-close programming error, not an environmental issue.
Source
Thrown at mempalace/backends/qdrant.py:704
client: _QdrantRESTClient,
config: _QdrantConfig,
palace: PalaceRef,
collection_name: str,
remote_collection: str,
):
self._backend = backend
self._client = client
self._config = config
self._palace = palace
self._collection_name = collection_name
self._remote_collection = remote_collection
self._lock = threading.RLock()
self._closed = False
self._known_dimension: Optional[int] = None
def _ensure_open(self) -> None:
if self._closed or self._backend._closed:
raise BackendClosedError("QdrantCollection has been closed")
def _remote_exists(self) -> bool:
return self._client.collection_exists(self._remote_collection)
def _marker_exists(self) -> bool:
return self._backend._marker_exists(self._palace)
def get_stored_embedder_identity(self):
return self._backend._get_embedder_identity(self._palace, self._collection_name)
def set_embedder_identity(self, identity) -> None:
# Sidecar-backed (see QdrantBackend), so this records even on a
# brand-new palace whose mismatch marker doesn't exist yet.
self._backend._set_embedder_identity(self._palace, self._collection_name, identity)
def _remote_dimension(self) -> Optional[int]:
try:
info = self._client.get_collection_info(self._remote_collection)View on GitHub (pinned to 06cb6987f0)
Solutions
- Re-open the backend and re-fetch the collection via palace.get_collection() after close
- Audit lifecycle: ensure close() only runs when no concurrent work holds the handle
- In long-running apps, create the backend per request/job or use a manager that re-opens on demand
- In tests, use fresh fixtures per test instead of sharing a closable handle
Example fix
# before
collection = palace.get_collection("notes")
backend.close()
collection.get(ids=["a"]) # BackendClosedError
# after
backend.close()
backend = qdrant_backend reopen (QdrantBackend(...))
collection = palace.get_collection("notes")
collection.get(ids=["a"]) Defensive patterns
Strategy: type-guard
Type guard
def collection_usable(collection) -> bool:
try:
collection._ensure_open()
return True
except Exception:
return False Try / catch
from mempalace.backends.base import BackendClosedError
try:
collection.get(ids=ids)
except BackendClosedError:
backend = reopen_backend()
collection = palace.get_collection(name) Prevention
- Fetch collection handles lazily right before use instead of caching across shutdown
- Ensure close() runs only after all worker threads/drains finish
- In tests, build a fresh backend per test via fixtures
When it happens
Trigger: Calling add/upsert/get/query/delete/facet_counts etc. on a collection handle obtained before backend.close() or collection.close(); a long-lived cached handle in a web app whose startup code closes the backend during shutdown while a request is still in flight.
Common situations: Module-level collection handle used after an atexit or context-manager exit closed the backend; test fixture closing the backend between tests while a helper still holds the old handle; background thread racing with shutdown.
Related errors
- PgVectorBackend has been closed
- QdrantBackend has been closed
- SQLiteExactCollection has been closed
- operator {k!r} not supported by chroma backend
- MilvusCollection has been closed
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/5a25e13055adba07.
Report an issue: GitHub.