MemPalace/mempalace · error · BackendClosedError

QdrantBackend has been closed

Error message

QdrantBackend has been closed

What it means

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.

Source

Thrown at mempalace/backends/qdrant.py:1303

    # presence signals "palace initialized" (reads raise CollectionNotInitialized
    # when the marker exists but the remote collection doesn't), so recording
    # identity at first empty open must not create it. The sidecar is unguarded,
    # so a brand-new palace can record identity immediately.
    @staticmethod
    def _embedder_sidecar_path(palace: PalaceRef) -> Optional[str]:
        if not palace.local_path:
            return None
        return os.path.join(palace.local_path, EMBEDDER_SIDECAR_FILENAME)

    def _get_embedder_identity(self, palace: PalaceRef, collection_name: str):
        return read_embedder_sidecar(self._embedder_sidecar_path(palace), collection_name)

    def _set_embedder_identity(self, palace: PalaceRef, collection_name: str, identity) -> None:
        write_embedder_sidecar(self._embedder_sidecar_path(palace), collection_name, identity)

    def _client(self, config: _QdrantConfig) -> _QdrantRESTClient:
        if self._closed:
            raise BackendClosedError("QdrantBackend has been closed")
        with self._lock:
            client = self._clients.get(config)
            if client is None:
                client = _QdrantRESTClient(config)
                self._clients[config] = client
            return client

    def _remote_collection_name(
        self,
        *,
        palace: PalaceRef,
        collection_name: str,
        config: _QdrantConfig,
    ) -> str:
        config = _QdrantConfig(
            url=config.url,
            api_key=config.api_key,
            timeout=config.timeout,

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Re-open the backend (get_backend('qdrant') returns a fresh instance after reset/close) and re-fetch collections before using them
  2. Fix lifecycle ordering so no threads/callbacks use the backend after close()
  3. In tests, obtain collection handles inside each test after backend setup rather than caching across reset_backends()

Example fix

// before
backend.close()
col.count()  # BackendClosedError via _client()
// after
backend.close()
backend = get_backend("qdrant")  # fresh instance
col = backend.get_collection(palace, "drawers")
col.count()
Defensive patterns

Strategy: try-catch

Try / catch

from mempalace.backends.base import BackendClosedError
try:
    col.count()
except BackendClosedError:
    backend = get_backend("qdrant")
    col = backend.get_collection(palace, name)
    col.count()

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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