MemPalace/mempalace · error · BackendClosedError
MilvusCollection has been closed
Error message
MilvusCollection has been closed
What it means
Raised by MilvusCollection._ensure_open() when an operation is attempted after either the collection handle or its parent backend was closed (self._closed or self._backend._closed). It is a BackendClosedError (BackendError subclass) — using a collection after backend.close() or collection.close() is a programming error, not a transient failure, so it is raised eagerly on every subsequent call.
Source
Thrown at mempalace/backends/milvus.py:342
config: _MilvusConfig,
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
self._known_native_lexical: Optional[bool] = None
def _ensure_open(self) -> None:
if self._closed or self._backend._closed:
raise BackendClosedError("MilvusCollection has been closed")
def _remote_exists(self) -> bool:
return bool(self._client.has_collection(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:
self._backend._set_embedder_identity(self._palace, self._collection_name, identity)
@property
def distance_metric(self) -> str:
return "cosine"
def _vector_score_to_distance(self, score: Any) -> float:View on GitHub (pinned to 06cb6987f0)
Solutions
- Re-obtain the collection from a live backend after close/reconnect: collection = backend.get_collection(...)
- Fix teardown ordering so nothing uses the collection after close (move close() to the outermost finally)
- Treat BackendClosedError as fatal for that handle — do not retry on the same object
Example fix
# before
backend.close()
results = collection.query(query_texts=[q], n_results=5) # stale handle
# after
backend.close()
backend = MilvusBackend.create_backend(options={})
collection = backend.get_collection(palace, "memory")
results = collection.query(query_texts=[q], n_results=5) Defensive patterns
Strategy: validation
Validate before calling
def ensure_open(collection) -> None:
if getattr(collection, "_closed", False) or getattr(getattr(collection, "_backend", None), "_closed", False):
raise RuntimeError("collection handle is stale — re-fetch from a live backend") Try / catch
from mempalace.backends.base import BackendClosedError
try:
results = collection.query(query_texts=[q], n_results=k)
except BackendClosedError:
backend = MilvusBackend.create_backend(options=opts) # reconnect, never retry the dead handle
collection = backend.get_collection(palace, name)
results = collection.query(query_texts=[q], n_results=k) Prevention
- Own one lifecycle: fetch collections from the live backend right before use
- Close backends only at the outermost shutdown point
- In tests, align fixture scopes so the collection fixture depends on the backend fixture
When it happens
Trigger: backend.close(); collection.query(...) — or keeping a cached collection handle across backend re-creation (e.g. in a long-lived MCP server that reconnects), or calling add() inside an atexit/finally path after shutdown already ran.
Common situations: Fixture teardown ordering in tests (backend closed by one fixture, used by another); connection-recycling logic that closes the old backend while workers still hold collection handles; MCP server restart races.
Related errors
- QdrantBackend has been closed
- SQLiteExactCollection has been closed
- milvus_consistency_level must be one of: {allowed}
- Milvus filters do not support null comparisons
- Milvus filter field {name!r} is not a safe identifier
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/eb5d93f591a8c974.
Report an issue: GitHub.