clockworklabs/SpacetimeDB · error · anyhow::Error
begin mutable transaction while one is already active
Error message
begin mutable transaction while one is already active
What it means
The dst engine target allows at most one active mutable transaction: execute(Interaction::BeginMutTx) asserts active_mut_tx.is_none(). This error means BeginMutTx was issued while a previous mutable transaction was still open — no CommitTx (or Replay, which implicitly discards the active tx) happened in between.
Source
Thrown at crates/dst/src/engine.rs:195
tables.push(TableDelta {
table,
inserts,
deletes,
truncated: entry.truncated,
});
}
tables.sort_by_key(|delta| delta.table);
CommitDelta { tables }
}
pub fn execute(&mut self, interaction: &Interaction) -> anyhow::Result<Observation> {
tracing::debug!(?interaction, "executing interaction");
let observation = match interaction {
Interaction::BeginMutTx => {
anyhow::ensure!(
self.active_mut_tx.is_none(),
"begin mutable transaction while one is already active"
);
let db = self
.db
.as_ref()
.ok_or_else(|| anyhow::anyhow!("database is not open"))?;
self.active_mut_tx = Some(db.begin_mut_tx(IsolationLevel::Serializable, Workload::Internal));
Ok(Observation::BeganMutTx)
}
Interaction::Insert { table, row } => {
let table_id = self.table_ids[*table];
let bytes = row_to_bytes(row);
let db = self
.db
.as_ref()
.ok_or_else(|| anyhow::anyhow!("database is not open"))?;
let tx = selfView on GitHub (pinned to 6dee26c6ef)
Solutions
- Fix the generator's state machine: only emit BeginMutTx when no transaction is active
- Emit CommitTx (or Replay) before the next BeginMutTx
- Track active-transaction state in the driver and assert the invariant between steps
Example fix
// before: two begins, no commit in between
vec![BeginMutTx, Insert { .. }, BeginMutTx, Delete { .. }, CommitTx]
// after: one transaction at a time
vec![BeginMutTx, Insert { .. }, CommitTx, BeginMutTx, Delete { .. }, CommitTx] Defensive patterns
Strategy: validation
Validate before calling
// Generator-side state check before emitting BeginMutTx
fn can_begin_mut_tx(tx_active: bool) -> bool {
!tx_active
} Prevention
- Model the interaction sequence as a state machine (idle -> tx-open -> idle) and generate transitions, not raw op lists
- Only emit BeginMutTx from the idle state; require CommitTx or Replay to return to idle
- Add assertions in the driver that BeganMutTx observations strictly alternate with Committed/Replayed ones
When it happens
Trigger: A generated interaction sequence like BeginMutTx, Insert, BeginMutTx — the workload generator emitted a second begin without an intervening CommitTx. Note Interaction::Replay silently drops the active transaction, so it also resets this state.
Common situations: Bugs in DST workload generators (WorkloadGen) or hand-written interaction scripts; state-machine modeling that forgets the tx-open edge.
Related errors
- insert without active mutable transaction
- delete without active mutable transaction
- commit without active mutable transaction
- database is not open
- cannot serialize refs without a typespace
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/d733671f684bc7de.
Report an issue: GitHub.