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

  1. Re-obtain the collection from a live backend after close/reconnect: collection = backend.get_collection(...)
  2. Fix teardown ordering so nothing uses the collection after close (move close() to the outermost finally)
  3. 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

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


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/eb5d93f591a8c974. Report an issue: GitHub.