nautechsystems/nautilus_trader · error
Failed to lock execution intent for nonce assignment: {e}
Error message
Failed to lock execution intent for nonce assignment: {e} What it means
In `assign_execution_intent_nonce_verified`, the code issues `SELECT ... FROM execution_intent WHERE id = $1 FOR UPDATE` inside a transaction to lock the intent row for a canonical nonce assignment. This error wraps any SQLx failure of that locking SELECT (connection loss, deadlock/lock timeout, syntax/schema mismatch, cancellation). The library throws it because nonce assignment must serialize on the intent row; if the lock query itself fails, the whole atomic operation is aborted and the caller gets this anyhow-wrapped error.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:5952
next_nonce == nonce,
"Execution nonce {} does not match canonical nonce {next_nonce}",
assignment.nonce
);
let (intent_chain_id, intent_wallet, intent_nonce, intent_status, intent_active) =
sqlx::query_as::<_, (i32, String, Option<i64>, String, bool)>(
"
SELECT chain_id, wallet_address, nonce, status, active
FROM execution_intent
WHERE id = $1
FOR UPDATE
",
)
.bind(assignment.intent_id)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| {
anyhow::anyhow!("Failed to lock execution intent for nonce assignment: {e}")
})?
.ok_or_else(|| {
anyhow::anyhow!("Execution intent {} was not found", assignment.intent_id)
})?;
anyhow::ensure!(
intent_chain_id == chain_id
&& intent_wallet == assignment.wallet_address
&& intent_status == "prepared"
&& intent_active
&& intent_nonce.is_none_or(|assigned| assigned == nonce),
"Execution intent {} cannot own canonical nonce {}",
assignment.intent_id,
assignment.nonce
);
for (index, decision) in assignment.decisions.iter().enumerate() {
let height_start = decision
.height_startView on GitHub (pinned to 18893faf8b)
Solutions
- Check database connectivity and pool health (connection limits, TLS, idle timeouts) and retry the operation; the transaction was rolled back so it is safe to re-run.
- Verify migrations have been applied so the execution_intent table and columns exist.
- Reduce lock contention: keep the pre-lock work short, avoid other transactions that lock execution_intent rows in a different order, and tune deadlock_timeout/lock_timeout.
- Inspect the inner `{e}` SQLx error (it is preserved in the message) for the concrete cause, e.g. `40P01 deadlock_detected` or `57P01 admin shutdown`.
Example fix
// before: firing many assignments concurrently for the same intent/wallet
let handles: Vec<_> = intents.iter().map(|a| db.assign_execution_intent_nonce_verified(a)).collect();
// after: serialize assignments per (chain_id, wallet) or retry on transient DB errors
for a in intents {
match db.assign_execution_intent_nonce_verified(a).await {
Ok(()) => {}
Err(e) if is_transient_db_error(&e) => backoff_retry(|| db.assign_execution_intent_nonce_verified(a)).await?,
Err(e) => return Err(e),
}
} Defensive patterns
Strategy: retry
Validate before calling
let exists: Option<i64> = sqlx::query_scalar("SELECT 1 FROM execution_intent WHERE id = $1").bind(&assignment.intent_id).fetch_optional(pool).await?;
anyhow::ensure!(exists.is_some(), "intent {} missing before assignment", assignment.intent_id); Try / catch
match db.assign_execution_intent_nonce_verified(&assignment).await {
Ok(()) => {}
Err(e) if e.to_string().contains("Failed to lock execution intent") && is_transient(&e) => backoff_retry(...).await?,
Err(e) => return Err(e),
} Prevention
- Apply all migrations before running assignment flows so execution_intent exists.
- Keep transactions short and lock execution_intent rows in a consistent order to avoid deadlocks.
- Set sane statement_timeout/lock_timeout and pool limits; monitor connection drops.
- Only retry on transient DB errors (connection resets, deadlocks, serialization failures); never on schema errors.
When it happens
Trigger: Calling `assign_execution_intent_nonce_verified` when: the Postgres connection drops mid-transaction (pool error, TLS failure); the SELECT ... FOR UPDATE deadlocks or exceeds lock_timeout because another transaction holds a conflicting lock on the same execution_intent row; the execution_intent table/columns are missing (unmigrated database); or the query is cancelled.
Common situations: Two workers assigning nonces for the same wallet concurrently causing lock contention or deadlock; database restart or failover during the call; running against a database without the execution_intent schema applied; long-running transactions hitting idle-in-transaction timeouts.
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
- Failed to lock migrated intent: {e}
- Failed to persist pre-sign verification: {e}
- Failed to insert into trader table: {e}
- Failed to insert into order table: {e}
- Failed to insert into position table: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/92596a6c385181b7.
Report an issue: GitHub.