tursodatabase/turso · error

database was busy

Error message

database was busy

What it means

Error propagated from turso_core statement stepping in the join benchmark's execute(): the database reported a busy condition, meaning a lock could not be acquired within the configured timeout (set via set_timeout/query_timeout_override) because another connection held the database. It surfaces to main when a benchmark query times out waiting for the database instead of completing.

Source

Thrown at perf/join-benchmark/main.rs:173

        });
    }
    Ok(queries)
}

fn set_timeout(statement: &mut turso_core::Statement, timeout_seconds: u64) {
    let timeout = (timeout_seconds != 0).then(|| Duration::from_secs(timeout_seconds));
    statement.set_query_timeout_override(Some(timeout));
}

fn execute(database: &Database, statement: &mut turso_core::Statement) -> Result<u64> {
    let mut result_rows = 0_u64;
    loop {
        match statement.step()? {
            StepResult::Row => result_rows = result_rows.saturating_add(1),
            StepResult::IO | StepResult::Yield | StepResult::Sleep { .. } => database.io.step()?,
            StepResult::Done => return Ok(result_rows),
            StepResult::Interrupt => bail!("query was interrupted"),
            StepResult::Busy => bail!("database was busy"),
        }
    }
}

fn print_plan(connection: &Arc<turso_core::Connection>, query: &Query) -> Result<()> {
    let sql = format!("EXPLAIN QUERY PLAN FORMAT=JSON {}", query.sql);
    let rows = connection.prepare(sql)?.run_collect_rows()?;
    let Some(Value::Text(plan)) = rows.first().and_then(|row| row.first()) else {
        bail!("query {} did not return a JSON plan", query.name);
    };
    let plan: JsonValue = serde_json::from_str(plan.as_str())?;
    println!("{}", json!({"query": query.name, "plan": plan}));
    Ok(())
}

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Ensure no other process/connection is using the database file: `lsof <db-file>` and close or kill holders.
  2. Run the benchmark against a private copy of the database file.
  3. Retry the benchmark after the conflicting transaction finishes.
  4. If concurrency is intentional, add busy-retry handling instead of failing immediately.

Example fix

// before
StepResult::Busy => bail!("database was busy"),
// after
StepResult::Busy => { std::thread::sleep(Duration::from_millis(50)); continue; }
Defensive patterns

Strategy: retry

Validate before calling

// before starting: ensure no other process holds the DB
// lsof <db-file>  → must be empty

Try / catch

loop {
    match statement.step() {
        Ok(StepResult::Busy) => {
            std::thread::sleep(Duration::from_millis(50));
            continue;
        }
        other => break other.map_err(Into::into),
    }
}

Prevention

When it happens

Trigger: statement.step() yields Busy during the execute loop, typically because another connection/process holds a conflicting lock on the database being benchmarked.

Common situations: Another tursodb/sqlite process has the database file open with an active write transaction; a leftover crashed process still holds the lock; running two benchmark instances against the same DB file concurrently.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-09-13). Data as JSON: /api/errors/21ea78bcf066a4a2. Report an issue: GitHub.