tursodatabase/turso · error · SqliteException

5

5

Error message

SQLite Error {errorCode}: '{message}'.

What it means

BackupDatabase deliberately throws SqliteException with native error code 5 (SQLITE_BUSY, 'database is locked') when the source connection has an active transaction. A physical copy cannot be taken while the connection's own transaction holds locks on the database; rather than attempting a backup that would contend, the provider translates this precondition into the standard busy error. This is raised before any schema or row copying starts.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteConnection.cs:486

        {
            _pendingExtensions.Add((file, proc));
            return;
        }

        LoadExtensionCore(file, proc);
    }

    public virtual void BackupDatabase(SqliteConnection destination)
        => BackupDatabase(destination, "main", "main");

    public virtual void BackupDatabase(SqliteConnection destination, string destinationName, string sourceName)
    {
        ThrowIfManagedLocalOnly(nameof(BackupDatabase));
        if (_database is null)
            throw new InvalidOperationException(Properties.Resources.CallRequiresOpenConnection("BackupDatabase"));
        ArgumentNullException.ThrowIfNull(destination);
        if (Transaction is not null)
            throw new SqliteException(Properties.Resources.SqliteNativeError(5, "database is locked"), 5);
        if (destination.State != ConnectionState.Open)
            destination.Open();

        foreach (var createSql in GetSchemaSql())
            destination.ExecuteNonQuery(createSql);

        foreach (var tableName in GetUserTableNames())
            CopyTableRows(destination, tableName);
    }

    public new virtual SqliteCommand CreateCommand() => new(this) { Transaction = Transaction };

    protected override DbCommand CreateDbCommand() => CreateCommand();

    protected override DbBatch CreateDbBatch()
    {
        if (_managedConnection is null)
        {

View on GitHub (pinned to 6c72522679)

Solutions

  1. Commit or roll back (and dispose) the source transaction before calling BackupDatabase.
  2. Restructure so backups run outside transaction scopes (e.g. a dedicated connection/path with no active Transaction).
  3. If you need a consistent copy mid-workflow, commit first, then back up, then continue in a new transaction.

Example fix

// before
using var conn = new SqliteConnection(cs);
conn.Open();
using var tx = conn.BeginTransaction();
InsertRows(conn);
conn.BackupDatabase(dst); // throws SqliteException code 5: database is locked

// after
using var conn = new SqliteConnection(cs);
conn.Open();
using (var tx = conn.BeginTransaction())
{
    InsertRows(conn);
    tx.Commit();
}
conn.BackupDatabase(dst); // no active transaction -> succeeds
Defensive patterns

Strategy: validation

Validate before calling

static void BackupOutsideTransactions(SqliteConnection src, string dstCs)
{
    if (src.Transaction is not null)
        throw new InvalidOperationException($"Commit or roll back the active transaction before backup.");
    if (src.State != ConnectionState.Open) src.Open();
    using var dst = new SqliteConnection(dstCs);
    dst.Open();
    src.BackupDatabase(dst);
}

Type guard

static bool HasActiveTransaction(SqliteConnection conn) => conn.Transaction is not null;

Try / catch

try
{
    src.BackupDatabase(dst);
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
{
    // SQLITE_BUSY from an active source transaction (or external locker):
    // settle transactions, then retry the backup once.
    throw new InvalidOperationException("Backup blocked by an active transaction; commit/rollback and retry.", ex);
}

Prevention

When it happens

Trigger: 'using var tx = conn.BeginTransaction(); ... conn.BackupDatabase(dst);' on the same connection; calling BackupDatabase from code that runs inside a unit-of-work scope holding a transaction; committing asynchronously and backing up before the commit lands.

Common situations: Backup jobs triggered while writes are in flight, repository-level backup helpers invoked inside transactional service methods, and test teardown that snapshots the database without unwinding the test transaction.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/3d830c3b8d909809. Report an issue: GitHub.