nautechsystems/nautilus_trader · error · anyhow::Error

Failed to mark execution {event} emitted: {e}

Error message

Failed to mark execution {event} emitted: {e}

What it means

Wrapped sqlx error from the UPDATE statement executed by mark_execution_event_emitted (database.rs:3885-3899). The event parameter selects one of two statements: 'fill' sets fill_emitted (and deactivates when status = 'finalized'), 'terminal' sets terminal_emitted (and deactivates when status is 'finalized' or 'reverted'); both guard against the opposing marker in the WHERE clause. This error means the chosen UPDATE failed at the database level; an unknown event string is a separate bail, and a zero-row match is error 98.

Source

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

        event: &str,
    ) -> anyhow::Result<()> {
        let statement = match event {
            "acknowledgement" => {
                "UPDATE execution_intent SET acknowledgement_emitted = TRUE, updated_at = NOW() WHERE id = $1"
            }
            "fill" => {
                "UPDATE execution_intent SET fill_emitted = TRUE, active = CASE WHEN status = 'finalized' THEN FALSE ELSE active END, updated_at = NOW() WHERE id = $1 AND NOT terminal_emitted"
            }
            "terminal" => {
                "UPDATE execution_intent SET terminal_emitted = TRUE, active = CASE WHEN status IN ('finalized', 'reverted') THEN FALSE ELSE active END, updated_at = NOW() WHERE id = $1 AND NOT fill_emitted"
            }
            _ => anyhow::bail!("Unknown execution event marker {event}"),
        };
        let result = sqlx::query(statement)
            .bind(intent_id)
            .execute(&self.pool)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to mark execution {event} emitted: {e}"))?;
        anyhow::ensure!(
            result.rows_affected() == 1,
            "Execution intent {intent_id} cannot mark {event} emitted"
        );
        Ok(())
    }

    /// Updates the status of a persisted execution transaction record.
    ///
    /// # Errors
    ///
    /// Returns an error if the database operation fails.
    pub async fn update_execution_transaction_status(
        &self,
        chain_id: u32,
        transaction_hash: &str,
        status: &str,
    ) -> anyhow::Result<()> {

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Downcast to sqlx::Error to classify the cause (connectivity, constraint, or schema)
  2. Apply migrations so execution_intent has fill_emitted, terminal_emitted, active, and updated_at
  3. Retry the marking on transient classes - the guard clauses make re-marking the same event idempotent (rows still match)
  4. If the dispatcher already emitted the event upstream, prioritize retrying the marker so events are not re-emitted after a restart
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: the event kind must be one the statements cover
anyhow::ensure!(
    matches!(event, "acknowledgement" | "fill" | "terminal"),
    "unknown execution event {event}"
);

Type guard

fn is_transient_db_error(err: &anyhow::Error) -> bool {
    err.downcast_ref::<sqlx::Error>().map_or(false, |e| {
        matches!(e, sqlx::Error::PoolTimedOut | sqlx::Error::PoolClosed | sqlx::Error::Io(_))
            || e.as_database_error().and_then(|d| d.code()).map_or(false, |c| {
                matches!(c.as_ref(), "08000" | "08003" | "08006" | "57014" | "40001" | "40P01")
            })
    })
}

Try / catch

match db.mark_execution_event_emitted(intent_id, event).await {
    Ok(()) => Ok(()),
    Err(e) if is_transient_db_error(&e) => {
        // the guard clauses make re-marking the same event idempotent, so retry hard:
        // skipping it risks duplicate order-event emission after a restart
        retry_with_backoff(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Connection loss or pool timeout while marking the event; a CHECK constraint rejecting an unexpected value; schema drift where fill_emitted/terminal_emitted/active/updated_at columns are missing; Postgres restart between acquiring the pooled connection and executing the UPDATE.

Common situations: Event dispatch loop hitting a Postgres blip after emitting an order event but before marking it; database created before event-marker columns were added by migrations; concurrent marking from multiple dispatcher tasks.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/50c001ee58860f88. Report an issue: GitHub.