{"record":{"id":"cd0ceca589fc499e","repo":"clockworklabs/SpacetimeDB","slug":"unable-to-lock-database-for-environment-metadata","errorCode":null,"errorMessage":"unable to lock database for environment metadata","messagePattern":"unable to lock database for environment metadata","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/host/host_controller.rs","lineNumber":818,"sourceCode":"        let guard = self.acquire_read_lock(replica_id).await.map_err(|_| {\n            warn!(\"timeout waiting for read lock on replica {replica_id} in `get_module_host`\");\n            NoSuchModule\n        })?;\n        guard\n            .as_ref()\n            .map(|Host { module, .. }| module.borrow().clone())\n            .ok_or(NoSuchModule)\n    }\n\n    /// Read environment metadata while preventing module replacement or shutdown.\n    pub async fn environment_metadata(\n        &self,\n        replica_id: u64,\n    ) -> anyhow::Result<spacetimedb_client_api_messages::publish::EnvironmentMetadata> {\n        let guard = self\n            .acquire_read_lock(replica_id)\n            .await\n            .map_err(|_| anyhow::anyhow!(\"unable to lock database for environment metadata\"))?;\n        let module = guard.as_ref().ok_or(NoSuchModule)?.module.borrow().clone();\n        let stored_keys = module.relational_db().with_read_only(Workload::Internal, |tx| {\n            crate::db::environment::snapshot(tx).map(|values| values.into_keys().collect())\n        })?;\n        Ok(spacetimedb_client_api_messages::publish::EnvironmentMetadata {\n            module_version: module.info.module_hash.to_string(),\n            declarations: module.info.module_def.environment().declarations().cloned().collect(),\n            stored_keys,\n        })\n    }\n\n    /// Subscribe to updates of the [`ModuleHost`] identified by `replica_id`,\n    /// or return an error if it is not registered with the controller.\n    ///\n    /// See [`Self::watch_maybe_launch_module_host`] for a variant which\n    /// launches the host if it is not running.\n    #[tracing::instrument(level = \"trace\", skip_all)]\n    pub async fn watch_module_host(&self, replica_id: u64) -> Result<watch::Receiver<ModuleHost>, NoSuchModule> {","sourceCodeStart":800,"sourceCodeEnd":836,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/core/src/host/host_controller.rs#L800-L836","documentation":"This error is thrown when a read lock on the module host for a replica cannot be acquired while fetching environment metadata. The controller uses a tokio RwLock per replica; if a write lock (e.g. a module update) is held for too long or the lock is poisoned/closed, acquire_read_lock fails and the caller gets this generic anyhow error instead of metadata. It is a transient concurrency failure, not data corruption.","triggerScenarios":"Calling the environment-metadata API (which returns spacetimedb_client_api_messages::publish::EnvironmentMetadata) while the replica's module host is locked by another operation, e.g. an in-flight publish/update_database of the same database, or during shutdown when the lock is closed.","commonSituations":"A developer queries database metadata via HTTP API at the same time another client republishes the module; CI scripts racing a `spacetime publish` with metadata reads; querying during server shutdown.","solutions":["Retry the metadata request after a short delay; the lock is usually released once the update completes.","Avoid issuing metadata reads concurrently with publish/update calls for the same database.","If it persists, check server logs for a stuck module update holding the replica write lock and restart the host process if needed."],"exampleFix":"// before: fire-and-forget concurrent calls\nmetadata = await fetchMetadata(db);\nawait publish(db, bytes);\n\n// after: sequence operations or retry with backoff\nawait publish(db, bytes);\nlet metadata = await retry(() => fetchMetadata(db), { retries: 3, backoffMs: 250 });","handlingStrategy":"retry","validationCode":"// no pre-check available; treat as transient\nif (err.message.includes('unable to lock database for environment metadata')) scheduleRetry();","typeGuard":null,"tryCatchPattern":"try { meta = await fetchMetadata(db); } catch (e) { if (/unable to lock database/.test(e.message)) await sleep(250).then(fetchMetadata); else throw e; }","preventionTips":["Don't interleave metadata reads with publishes for the same database","Apply exponential backoff on lock-related failures"],"tags":["concurrency","locking","host-controller","metadata"],"backgroundTag":"request-timeout","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-09-20T12:15:59.611Z","contentChangedAt":"2026-09-20T12:15:59.611Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}