{"record":{"id":"c6632fbb545f2c58","repo":"MemPalace/mempalace","slug":"qdrantbackend-has-been-closed","errorCode":null,"errorMessage":"QdrantBackend has been closed","messagePattern":"QdrantBackend has been closed","errorType":"exception","errorClass":"BackendClosedError","httpStatus":null,"severity":"error","filePath":"mempalace/backends/qdrant.py","lineNumber":1303,"sourceCode":"    # presence signals \"palace initialized\" (reads raise CollectionNotInitialized\n    # when the marker exists but the remote collection doesn't), so recording\n    # identity at first empty open must not create it. The sidecar is unguarded,\n    # so a brand-new palace can record identity immediately.\n    @staticmethod\n    def _embedder_sidecar_path(palace: PalaceRef) -> Optional[str]:\n        if not palace.local_path:\n            return None\n        return os.path.join(palace.local_path, EMBEDDER_SIDECAR_FILENAME)\n\n    def _get_embedder_identity(self, palace: PalaceRef, collection_name: str):\n        return read_embedder_sidecar(self._embedder_sidecar_path(palace), collection_name)\n\n    def _set_embedder_identity(self, palace: PalaceRef, collection_name: str, identity) -> None:\n        write_embedder_sidecar(self._embedder_sidecar_path(palace), collection_name, identity)\n\n    def _client(self, config: _QdrantConfig) -> _QdrantRESTClient:\n        if self._closed:\n            raise BackendClosedError(\"QdrantBackend has been closed\")\n        with self._lock:\n            client = self._clients.get(config)\n            if client is None:\n                client = _QdrantRESTClient(config)\n                self._clients[config] = client\n            return client\n\n    def _remote_collection_name(\n        self,\n        *,\n        palace: PalaceRef,\n        collection_name: str,\n        config: _QdrantConfig,\n    ) -> str:\n        config = _QdrantConfig(\n            url=config.url,\n            api_key=config.api_key,\n            timeout=config.timeout,","sourceCodeStart":1285,"sourceCodeEnd":1321,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/backends/qdrant.py#L1285-L1321","documentation":"Raised by QdrantBackend._client() when the backend instance has been closed (close() called) and any operation needs an HTTP client. All collections share the backend's client cache, so after close() every subsequent call through this backend fails fast rather than attempting HTTP on a torn-down object.","triggerScenarios":"Calling any collection operation after backend.close() (or reset_backends() in tests) — including via a collection handle obtained before closing, since collections hold the backend reference. Also hit when a test fixture closes the backend in teardown but a lingering handle is used afterwards.","commonSituations":"App shutdown ordering: background thread/hook fires after the main path closed the backend; tests that call reset_backends() for isolation then reuse a stale collection object; keeping a module-level collection handle across a CLI re-invocation in the same process.","solutions":["Re-open the backend (get_backend('qdrant') returns a fresh instance after reset/close) and re-fetch collections before using them","Fix lifecycle ordering so no threads/callbacks use the backend after close()","In tests, obtain collection handles inside each test after backend setup rather than caching across reset_backends()"],"exampleFix":"// before\nbackend.close()\ncol.count()  # BackendClosedError via _client()\n// after\nbackend.close()\nbackend = get_backend(\"qdrant\")  # fresh instance\ncol = backend.get_collection(palace, \"drawers\")\ncol.count()","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from mempalace.backends.base import BackendClosedError\ntry:\n    col.count()\nexcept BackendClosedError:\n    backend = get_backend(\"qdrant\")\n    col = backend.get_collection(palace, name)\n    col.count()","preventionTips":["Close backends exactly once at process exit, after all workers stop","Don't cache collection handles across backend resets in tests","Add a is-closed guard in background tasks before touching the backend"],"tags":["qdrant","lifecycle","closed-handle"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}