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
- Ensure no other process/connection is using the database file: `lsof <db-file>` and close or kill holders.
- Run the benchmark against a private copy of the database file.
- Retry the benchmark after the conflicting transaction finishes.
- 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
- Run benchmarks against a private copy of the database file
- Ensure no other tursodb/sqlite process has the file open during runs
- Add bounded busy-retry with backoff in benchmark harnesses
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
- no SQL files matched the query selection
- query was interrupted
- query {} did not return a JSON plan
- InvalidData
- UnexpectedEof
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-09-13).
Data as JSON: /api/errors/21ea78bcf066a4a2.
Report an issue: GitHub.