clockworklabs/SpacetimeDB · critical

TODO: retry?

Error message

TODO: retry?

What it means

RelationalDB::finish_tx commits a MutTx on the Ok path; commit_tx returns Ok(None) when the transaction conflicted with a concurrent commit and must be retried (optimistic concurrency detected at commit time). The retry path was never implemented, so instead of retrying, the host panics with 'TODO: retry?'. You hit it when two transactions write the same rows and the loser detects the conflict only at commit; note the whole transaction is discarded, so callers cannot recover the work in-process.

Source

Thrown at crates/engine/src/relational_db.rs:1046

        let (_tx_offset, tx_metrics, reducer) = self.release_tx(tx);
        self.report_read_tx_metrics(reducer, tx_metrics);
        res
    }

    /// Perform the transactional logic for the `tx` according to the `res`
    pub fn finish_tx<A, E>(&self, tx: MutTx, res: Result<A, E>) -> Result<A, E>
    where
        E: From<DBError>,
    {
        if res.is_err() {
            let (_, tx_metrics, reducer) = self.rollback_mut_tx(tx);
            self.report_mut_tx_metrics(reducer, tx_metrics, None);
        } else {
            match self.commit_tx(tx).map_err(E::from)? {
                Some((_tx_offset, tx_data, tx_metrics, reducer)) => {
                    self.report_mut_tx_metrics(reducer, tx_metrics, Some(tx_data));
                }
                None => panic!("TODO: retry?"),
            }
        }

        res
    }

    /// Roll back transaction `tx` if `res` is `Err`, otherwise return it
    /// alongside the `Ok` value.
    pub fn rollback_on_err<A, E>(&self, tx: MutTx, res: Result<A, E>) -> Result<(MutTx, A), E> {
        match res {
            Err(e) => {
                let (_, tx_metrics, reducer) = self.rollback_mut_tx(tx);
                self.report_mut_tx_metrics(reducer, tx_metrics, None);

                Err(e)
            }
            Ok(a) => Ok((tx, a)),
        }

View on GitHub (pinned to 3653d2ed49)

Solutions

  1. Reduce contention: shard the hot row (e.g. N counter rows aggregated in a view) or serialize updates through a single reducer path.
  2. Retry the operation at the caller or module boundary with backoff, since the transaction is valid but lost the race.
  3. Upgrade spacetimedb and check the changelog for a release that retries commit conflicts instead of panicking.
  4. Keep write transactions short to shrink the conflict window.

Example fix

// before: many clients concurrently update one hot row
#[reducer]
fn bump(ctx: &ReducerContext) { ctx.db.counter().update(|c| c.n += 1); }

// after: shard the counter so concurrent commits rarely conflict
#[reducer]
fn bump(ctx: &ReducerContext) {
    let shard = shard_for(ctx.sender) % SHARDS;
    ctx.db.counter().shard(shard).update(|c| c.n += 1);
}
Defensive patterns

Strategy: retry

Try / catch

// at the invocation boundary: retry the whole reducer on commit conflict
let mut attempt = 0;
loop {
    match std::panic::catch_unwind(AssertUnwindSafe(|| execute_reducer_call(call.clone()))) {
        Ok(res) => break res,
        Err(p) if is_commit_conflict_panic(&p) && attempt < 5 => {
            attempt += 1;
            std::thread::sleep(backoff(attempt)); // 10ms, 40ms, 160ms, ...
        }
        Err(p) => std::panic::resume_unwind(p),
    }
}

fn is_commit_conflict_panic(p: &(dyn std::any::Any + Send)) -> bool {
    let msg = p.downcast_ref::<String>().map(|s| s.as_str())
        .or_else(|| p.downcast_ref::<&'static str>().copied());
    msg.map_or(false, |m| m.contains("TODO: retry?"))
}

Prevention

When it happens

Trigger: Concurrent transactions/reducers writing overlapping rows such that commit_tx cannot validate the read/write set and returns Ok(None) inside finish_tx.

Common situations: Hot rows or counters updated by many concurrent reducer calls; load tests with parallel writers; long-running transactions widening the conflict window.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@3653d2ed49 (2026-08-20). Data as JSON: /api/errors/9dd6f12cfae21e3c. Report an issue: GitHub.