{"record":{"id":"97455e73b870d31d","repo":"MemPalace/mempalace","slug":"pgvectorbackend-has-been-closed","errorCode":null,"errorMessage":"PgVectorBackend has been closed","messagePattern":"PgVectorBackend has been closed","errorType":"exception","errorClass":"BackendClosedError","httpStatus":null,"severity":"error","filePath":"mempalace/backends/pgvector.py","lineNumber":1490,"sourceCode":"    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    # ------------------------------------------------------------------\n    def _client(self, config: _PgVectorConfig) -> _PgVectorClient:\n        with self._lock:\n            # Checked under the lock so a client cannot be created and stored\n            # concurrently with close() clearing the registry (mirrors\n            # SQLiteExactBackend._connect).\n            if self._closed:\n                raise BackendClosedError(\"PgVectorBackend has been closed\")\n            client = self._clients.get(config)\n            if client is None:\n                client = _PgVectorClient(config)\n                self._clients[config] = client\n            return client\n\n    def get_collection(self, *args, **kwargs) -> PgVectorCollection:\n        palace, collection_name, create, options = self._normalize_args(args, kwargs)\n        config = _PgVectorConfig.from_options(options)\n        if palace.namespace and palace.namespace != config.namespace:\n            config = _PgVectorConfig(dsn=config.dsn, namespace=palace.namespace)\n        client = self._client(config)\n        if palace.local_path:\n            marker_path = self._marker_path(palace.local_path)\n            if os.path.isfile(marker_path):\n                self._validate_marker_target(palace, config)\n            elif not create:\n                raise PalaceNotFoundError(marker_path)","sourceCodeStart":1472,"sourceCodeEnd":1508,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/backends/pgvector.py#L1472-L1508","documentation":"PgVectorBackend caches one _PgVectorClient per config in a registry guarded by a lock. close() sets _closed and clears the registry; _client() re-checks _closed under the same lock and raises BackendClosedError on any subsequent use. This prevents use-after-close races where a client is created concurrently with shutdown (the same pattern as SQLiteExactBackend._connect).","triggerScenarios":"Calling backend.get_collection(...) (or any path reaching _client()) after backend.close(); a background thread opening a collection while the main thread shuts the backend down at interpreter exit.","commonSituations":"Module-teardown hooks closing the backend before pending async tasks run; a long-lived palace object used across test cases where a fixture closes the backend; concurrent request handlers racing shutdown.","solutions":["Order your shutdown: cancel/finish outstanding work before calling backend.close().","If the backend must keep serving, create a fresh PgVectorBackend instance instead of reusing the closed one.","In tests, use a fresh backend per test (fixture) rather than a shared closed instance."],"exampleFix":"# before\nbackend.close()\ncol = backend.get_collection(palace, \"notes\")  # BackendClosedError\n\n# after\n# finish work first, then close\ncol.query(query_embeddings=[vec])\nbackend.close()","handlingStrategy":"try-catch","validationCode":"if backend._closed:  # internal flag; prefer tracking closes yourself\n    raise RuntimeError(\"backend already closed\")\ncol = backend.get_collection(palace, \"notes\")","typeGuard":"def is_open(backend) -> bool:\n    return not getattr(backend, \"_closed\", False)","tryCatchPattern":"from mempalace.backends.base import BackendClosedError\ntry:\n    col = backend.get_collection(palace, \"notes\")\nexcept BackendClosedError:\n    backend = PgVectorBackend()\n    col = backend.get_collection(palace, \"notes\")","preventionTips":["Own an explicit shutdown order: stop workers, await pending tasks, then close().","Register close() in atexit/atypical shutdown only after background work is joined.","Use one backend instance per process lifetime and never reuse after close."],"tags":["pgvector","lifecycle","use-after-close","concurrency"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}