clockworklabs/SpacetimeDB · error · anyhow::Error

database is not open

Error message

database is not open

What it means

In the dst (differential system testing) engine target, count_state() dereferences self.db, which is None whenever no database is open. init() always opens one, so in practice this fires after reopen_from_commitlog took the database and the re-open failed — i.e. an earlier Replay interaction failed and left the target without a db, and the next Replay (which calls count_state) or count_state itself hits the None.

Source

Thrown at crates/dst/src/engine.rs:138

    }

    fn reopen_from_commitlog(&mut self) -> anyhow::Result<()> {
        let db = self
            .db
            .take()
            .ok_or_else(|| anyhow::anyhow!("replay without open database"))?;

        drop(db);

        self.db = Some(Self::open_db(&self.commitlog, self.runtime_handle.clone())?);
        Ok(())
    }

    fn count_state(&self) -> anyhow::Result<CountState> {
        let db = self
            .db
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("database is not open"))?;
        let tx = db.begin_tx(Workload::Internal);
        let mut row_counts = Vec::with_capacity(self.table_ids.len());

        for (table, table_id) in self.table_ids.iter().enumerate() {
            let count = match db.iter(&tx, *table_id) {
                Ok(iter) => iter.count() as u64,
                Err(err) => {
                    let _ = db.release_tx(tx);
                    return Err(err.into());
                }
            };
            row_counts.push(TableRowCount { table, count });
        }

        let _ = db.release_tx(tx);
        Ok(CountState { row_counts })
    }

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Abort the test run on the first interaction error — target state is no longer valid
  2. Rebuild the target via EngineTarget::init instead of continuing
  3. Diagnose the underlying open/replay failure from the first error, not this message

Example fix

// before: keep driving the target after Replay failed
engine.execute(&Interaction::Replay)?;

// after: treat any interaction error as fatal for this run
if let Err(e) = engine.execute(&Interaction::Replay) {
    tracing::error!(%e, "target broken; ending run");
    return;
}
Defensive patterns

Strategy: validation

Validate before calling

fn target_is_usable(target: &EngineTarget, last_ok: bool) -> bool {
    last_ok // db is Some after successful init/reopen; a failed Replay leaves it None
}

Try / catch

In the driver, match on the execute() Result: on any Err, terminate the run for this target (its db may be gone) rather than issuing further interactions.

Prevention

When it happens

Trigger: Issuing another Replay interaction after a prior Replay already failed inside open_db (commitlog replay error); calling count_state on a target whose init never completed; a driver that ignores an earlier error observation and keeps driving the target.

Common situations: DST/fuzz runs where the workload continues after a failed interaction; test drivers that do not stop on first error.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/c91236fc0e5751b3. Report an issue: GitHub.