nautechsystems/nautilus_trader · error · anyhow::Error
Failed to lock execution intent
Error message
Failed to lock execution intent {intent_id}: {e} What it means
The FOR UPDATE row lock on the execution_intent row failed at the database level. The library wraps the sqlx error so the caller knows it could not acquire a lock on the intent row, which is required to safely advance the intent's status during signing persistence.
Solutions
- Retry the operation after a short backoff — the conflicting transaction usually commits or rolls back quickly.
- Check pg_locks / pg_stat_activity for the transaction blocking the execution_intent row and resolve the long-running holder.
- Increase statement/lock timeout if contention is expected, or shorten surrounding transactions.
- Verify database connectivity and re-establish the pool if the error indicates connection loss.
Defensive patterns
Strategy: retry
Validate before calling
// pre-check contention
let blocked = sqlx::query_scalar::<_, i64>("SELECT count(*) FROM pg_locks WHERE relation = 'execution_intent'::regclass AND NOT granted").fetch_one(&pool).await?;
if blocked > 0 { tokio::time::sleep(Duration::from_millis(200)).await; } Try / catch
let row = retry(3, backoff(100).factor(2.0), || async {
db.add_execution_transaction(intent_id, chain_id, hash, sealed).await
}).await.context("failed to lock execution intent after retries")?; Prevention
- Keep transactions that hold the FOR UPDATE lock short
- Set explicit lock_timeout and retry transient lock failures with jittered backoff
- Use a single owner (queue/leader) per intent to avoid lock contention
- Monitor pg_locks for waiting transactions on execution_intent
When it happens
Trigger: The SELECT ... FOR UPDATE on execution_intent WHERE id = $1 returned a database error: connection loss mid-query, statement timeout while another transaction holds the row lock, serialization/deadlock abort, or the row being blocked by a long-running transaction.
Common situations: Two workers persisting signed transactions for the same intent concurrently with long outer transactions; a stuck transaction holding the lock until lock_timeout fires; network blip to PostgreSQL during the statement.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- Execution payload changed during rollback
- Execution payload changed while clearing rollback envelope
- Execution payload storage is not rolling back
- Failed to lock execution verification state
- Failed to lock finality nonce ledger
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/0e4486cf81afcb16.
Report an issue: GitHub.
Appendix: source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6658
let marker = sqlx::query_scalar::<_, bool>(
"SELECT EXISTS (SELECT 1 FROM execution_schema_version WHERE component = $1)",
)
.bind(EXECUTION_PAYLOAD_COMPONENT)
.fetch_one(&mut *transaction)
.await
.context("failed to inspect execution payload marker")?;
anyhow::ensure!(
!marker,
"Plaintext signed transaction persistence is disabled after payload activation"
);
}
let current_status = sqlx::query_scalar::<_, String>(
"SELECT status FROM execution_intent WHERE id = $1 FOR UPDATE",
)
.bind(intent_id)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to lock execution intent {intent_id}: {e}"))?
.ok_or_else(|| anyhow::anyhow!("Execution intent {intent_id} was not found"))?;
anyhow::ensure!(
current_status == TransactionStatus::Prepared.as_str()
|| current_status == TransactionStatus::Signed.as_str(),
"Execution intent {intent_id} is {current_status}, not prepared for signing"
);
let row = sqlx::query_as::<_, ExecutionTransactionHashRow>(
"
INSERT INTO execution_transaction_hash (
intent_id, chain_id, transaction_hash, payload_expected,
raw_transaction, sealed_transaction, status
)
SELECT id, chain_id, $3, TRUE, $4, $5, 'signed'
FROM execution_intent
WHERE id = $1 AND chain_id = $2 AND nonce IS NOT NULL
ON CONFLICT (chain_id, transaction_hash) DO UPDATE
SET transaction_hash = EXCLUDED.transaction_hashView on GitHub (pinned to 18893faf8b)