{"record":{"id":"5a25e13055adba07","repo":"MemPalace/mempalace","slug":"qdrantcollection-has-been-closed","errorCode":null,"errorMessage":"QdrantCollection has been closed","messagePattern":"QdrantCollection has been closed","errorType":"exception","errorClass":"BackendClosedError","httpStatus":null,"severity":"error","filePath":"mempalace/backends/qdrant.py","lineNumber":704,"sourceCode":"        client: _QdrantRESTClient,\n        config: _QdrantConfig,\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\n    def _ensure_open(self) -> None:\n        if self._closed or self._backend._closed:\n            raise BackendClosedError(\"QdrantCollection has been closed\")\n\n    def _remote_exists(self) -> bool:\n        return self._client.collection_exists(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        # Sidecar-backed (see QdrantBackend), so this records even on a\n        # brand-new palace whose mismatch marker doesn't exist yet.\n        self._backend._set_embedder_identity(self._palace, self._collection_name, identity)\n\n    def _remote_dimension(self) -> Optional[int]:\n        try:\n            info = self._client.get_collection_info(self._remote_collection)","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/backends/qdrant.py#L686-L722","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\n collection = palace.get_collection(\"notes\")\n backend.close()\n collection.get(ids=[\"a\"])  # BackendClosedError\n# after\n backend.close()\n backend = qdrant_backend reopen (QdrantBackend(...))\n collection = palace.get_collection(\"notes\")\n collection.get(ids=[\"a\"])","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"def collection_usable(collection) -> bool:\n    try:\n        collection._ensure_open()\n        return True\n    except Exception:\n        return False","tryCatchPattern":"from mempalace.backends.base import BackendClosedError\ntry:\n    collection.get(ids=ids)\nexcept BackendClosedError:\n    backend = reopen_backend()\n    collection = palace.get_collection(name)","preventionTips":["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"],"tags":["lifecycle","use-after-close","qdrant","concurrency"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}