{"record":{"id":"eb5d93f591a8c974","repo":"MemPalace/mempalace","slug":"milvuscollection-has-been-closed","errorCode":null,"errorMessage":"MilvusCollection has been closed","messagePattern":"MilvusCollection has been closed","errorType":"exception","errorClass":"BackendClosedError","httpStatus":null,"severity":"error","filePath":"mempalace/backends/milvus.py","lineNumber":342,"sourceCode":"        config: _MilvusConfig,\n        palace: PalaceRef,\n        collection_name: str,\n        remote_collection: str,\n    ):\n        self._backend = backend\n        self._client = client\n        self._config = config\n        self._palace = palace\n        self._collection_name = collection_name\n        self._remote_collection = remote_collection\n        self._lock = threading.RLock()\n        self._closed = False\n        self._known_dimension: Optional[int] = None\n        self._known_native_lexical: Optional[bool] = None\n\n    def _ensure_open(self) -> None:\n        if self._closed or self._backend._closed:\n            raise BackendClosedError(\"MilvusCollection has been closed\")\n\n    def _remote_exists(self) -> bool:\n        return bool(self._client.has_collection(self._remote_collection))\n\n    def _marker_exists(self) -> bool:\n        return self._backend._marker_exists(self._palace)\n\n    def get_stored_embedder_identity(self):\n        return self._backend._get_embedder_identity(self._palace, self._collection_name)\n\n    def set_embedder_identity(self, identity) -> None:\n        self._backend._set_embedder_identity(self._palace, self._collection_name, identity)\n\n    @property\n    def distance_metric(self) -> str:\n        return \"cosine\"\n\n    def _vector_score_to_distance(self, score: Any) -> float:","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/backends/milvus.py#L324-L360","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nbackend.close()\nresults = collection.query(query_texts=[q], n_results=5)  # stale handle\n\n# after\nbackend.close()\nbackend = MilvusBackend.create_backend(options={})\ncollection = backend.get_collection(palace, \"memory\")\nresults = collection.query(query_texts=[q], n_results=5)","handlingStrategy":"validation","validationCode":"def ensure_open(collection) -> None:\n    if getattr(collection, \"_closed\", False) or getattr(getattr(collection, \"_backend\", None), \"_closed\", False):\n        raise RuntimeError(\"collection handle is stale — re-fetch from a live backend\")","typeGuard":null,"tryCatchPattern":"from mempalace.backends.base import BackendClosedError\ntry:\n    results = collection.query(query_texts=[q], n_results=k)\nexcept BackendClosedError:\n    backend = MilvusBackend.create_backend(options=opts)  # reconnect, never retry the dead handle\n    collection = backend.get_collection(palace, name)\n    results = collection.query(query_texts=[q], n_results=k)","preventionTips":["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"],"tags":["milvus","lifecycle","closed-handle","backend"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}