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
- Ensure migrations run from a single instance at a time (advisory lock or leader election)
- Find and terminate blocking sessions via pg_stat_activity / pg_locks
- Set a lock_timeout and retry the migration
- 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
- Gate migrations behind a Postgres advisory lock so only one instance migrates
- Set a lock_timeout to fail fast and retry
- Avoid long transactions/queries touching execution_transaction during deploys
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
- Failed to add pool event block hash storage
- Failed to inspect legacy execution transactions
- Failed to lock execution verification state
- Failed to lock migrated intent
- Failed to migrate execution_transaction table
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)