nautechsystems/nautilus_trader · error

Failed to send query add_trade to database message handler:

Error message

Failed to send query add_trade to database message handler: {e}

What it means

add_trade sends a DatabaseQuery::AddTrade over the cache's internal mpsc channel to the database message handler task; the error is produced by map_err when channel send fails. A send fails only when the receiver has been dropped, meaning the background handler is no longer running. The trade data itself is not validated here — the failure is purely internal plumbing.

Source

Thrown at crates/infrastructure/src/sql/cache.rs:1021

                    }
                }
                Err(e) => {
                    log::error!("Failed to load quotes for instrument {instrument_id}: {e:?}");
                    if let Err(e) = tx.send(Vec::new()) {
                        log::error!(
                            "Failed to send empty quotes for instrument {instrument_id}: {e:?}"
                        );
                    }
                }
            }
        });
        Ok(rx.recv()?)
    }

    fn add_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
        let query = DatabaseQuery::AddTrade(trade.to_owned());
        self.tx.send(query).map_err(|e| {
            anyhow::anyhow!("Failed to send query add_trade to database message handler: {e}")
        })
    }

    fn load_trades(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>> {
        let pool = self.pool.clone();
        let instrument_id = instrument_id.to_owned();
        let (tx, rx) = std::sync::mpsc::channel();

        tokio::spawn(async move {
            let result = DatabaseQueries::load_trades(&pool, &instrument_id).await;
            match result {
                Ok(trades) => {
                    if let Err(e) = tx.send(trades) {
                        log::error!("Failed to send trades for instrument {instrument_id}: {e:?}");
                    }
                }
                Err(e) => {
                    log::error!("Failed to load trades for instrument {instrument_id}: {e:?}");

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Confirm the database message handler task is running before calling add_trade
  2. Restart/recreate the cache adapter after any shutdown; do not write through stale handles
  3. Inspect handler logs for the reason the receiver was dropped (panic, pool failure) and fix it
  4. Wrap add_trade calls in error handling if trade persistence is non-critical
Defensive patterns

Strategy: try-catch

Try / catch

try:
    cache.add_trade(trade)
except Exception as e:
    if "Failed to send query add_trade" in str(e):
        logger.error("DB cache handler unavailable")
        raise PersistenceError from e

Prevention

When it happens

Trigger: Calling add_trade (or py_add_trade from Python) when the handler task backing the cache has terminated: adapter shut down, task aborted, or handler exited due to an earlier fatal DB error.

Common situations: A live node tears down its database adapter while a strategy callback still records trades; a handler panic from a previous failed write; using a cache instance obtained before reconnection.

Related errors


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