nautechsystems/nautilus_trader · error · anyhow::Error

Failed to lock legacy execution transactions

Error message

Failed to lock legacy execution transactions: {e}

What it means

This error wraps failure to acquire an ACCESS EXCLUSIVE lock on the execution_transaction table (`LOCK TABLE ... IN ACCESS EXCLUSIVE MODE`) during migration. The library throws it because DDL changes require excluding concurrent readers/writers, and the lock request failed.

Solutions

  1. Ensure migrations run from a single instance at a time (advisory lock or leader election)
  2. Find and terminate blocking sessions via pg_stat_activity / pg_locks
  3. Set a lock_timeout and retry the migration
  4. Confirm the execution_transaction table exists before locking

Example fix

// before
sqlx::query("LOCK TABLE execution_transaction IN ACCESS EXCLUSIVE MODE")
    .execute(&mut *transaction)
    .await
    .map_err(|e| anyhow::anyhow!("Failed to lock legacy execution transactions: {e}"))?;
// after
// pre-step: clear blockers
// SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND ...;
sqlx::query("SET LOCAL lock_timeout = '5s'; LOCK TABLE execution_transaction IN ACCESS EXCLUSIVE MODE")
    .execute(&mut *transaction)
    .await
    .map_err(|e| anyhow::anyhow!("Failed to lock legacy execution transactions: {e}"))?;
Defensive patterns

Strategy: retry

Validate before calling

let blockers = sqlx::query_scalar::<_, i64>(
    "SELECT count(*) FROM pg_locks l JOIN pg_class c ON l.relation = c.oid WHERE c.relname = 'execution_transaction'"
).fetch_one(&pool).await?;
if blockers > 0 { anyhow::bail!("execution_transaction is locked by another session"); }

Try / catch

for attempt in 0..5 {
    match run_migration().await {
        Ok(()) => break,
        Err(e) if e.to_string().contains("Failed to lock") && attempt < 4 => backoff(attempt).await,
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Running ensure_execution_transaction_schema while another transaction holds locks on execution_transaction (long queries, other migrations, autovacuum), lock_timeout exceeded, or the table does not exist yet.

Common situations: Concurrent app instances running migrations at startup against the same database; a long-running report query holding a shared lock; deadlock_timeout/lock_timeout defaults blocking the lock wait.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/369efd12dde7556c. Report an issue: GitHub.

Appendix: source

Thrown at crates/adapters/blockchain/src/cache/database.rs:3360

    /// The migration locks the legacy transaction table, refuses unresolved version 1 rows,
    /// installs the versioned intent and hash-history tables, and fences older writers before
    /// releasing the lock. This prevents a mixed-version process from bypassing the new signer
    /// ownership constraints.
    ///
    /// # Errors
    ///
    /// Returns an error if the database operation fails.
    pub async fn ensure_execution_transaction_schema(&self) -> anyhow::Result<()> {
        let mut transaction = self
            .pool
            .begin()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to start execution schema migration: {e}"))?;

        sqlx::query("LOCK TABLE execution_transaction IN ACCESS EXCLUSIVE MODE")
            .execute(&mut *transaction)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to lock legacy execution transactions: {e}"))?;

        for statement in [
            "
            ALTER TABLE execution_transaction
            ADD COLUMN IF NOT EXISTS client_order_id TEXT
            ",
            "
            ALTER TABLE execution_transaction
            ADD COLUMN IF NOT EXISTS wallet_address TEXT
            ",
            "
            ALTER TABLE execution_transaction
            ALTER COLUMN wallet_address DROP NOT NULL
            ",
            "
            CREATE TABLE IF NOT EXISTS execution_schema_version (
                component TEXT PRIMARY KEY,
                version SMALLINT NOT NULL CHECK (version > 0)

View on GitHub (pinned to 18893faf8b)