nautechsystems/nautilus_trader · error

load_state channel closed: {e}

Error message

load_state channel closed: {e}

What it means

`load_state` offloads Redis reads to a blocking worker thread and waits for the result over a `tokio::sync::mpsc` channel. This error means the receiver side (`blocking_recv`) returned `RecvError`, i.e. the channel closed because the sending task was dropped without sending a result — the worker thread panicked or exited early.

Source

Thrown at crates/infrastructure/src/redis/cache.rs:1293

        get_runtime().spawn(async move {
            let result = async {
                let full_key = format!("{trader_key}{REDIS_DELIMITER}{key}");
                let value: Option<Bytes> = con.get(&full_key).await?;
                let Some(value) = value else {
                    return Ok(AHashMap::new());
                };

                DatabaseQueries::deserialize_payload(encoding, &value)
            }
            .await;

            if let Err(e) = tx.send(result) {
                log::error!("Failed to send state load result for '{key}': {e:?}");
            }
        });

        blocking_recv(&rx).map_err(|e| anyhow::anyhow!("load_state channel closed: {e}"))?
    }

    fn update_state(&self, key: String, state: &AHashMap<String, Bytes>) -> anyhow::Result<()> {
        let payload = DatabaseQueries::serialize_payload(self.encoding(), state)?;
        self.database.insert(key, Some(vec![Bytes::from(payload)]))
    }

    fn replace_list(&self, key: String, payload: Bytes) -> anyhow::Result<()> {
        self.send_command(DatabaseOperation::ReplaceList, key, Some(vec![payload]))
    }
}

#[async_trait::async_trait]
impl CacheDatabaseFactory for RedisCacheConfig {
    async fn create(
        &self,
        trader_id: TraderId,
        instance_id: UUID4,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check application logs for 'Failed to send state load result' or a panic message from the worker thread just before this error — that reveals the root cause
  2. Retry the load; transient panics during shutdown usually clear on a fresh run
  3. Validate the Redis keys/state payloads for the given key deserialize with the configured encoding before loading
  4. If reproducible, wrap the worker body in catch_unwind or report send failures through the channel instead of only logging
Defensive patterns

Strategy: retry

Try / catch

// Rust
match cache.load_state(key) {
    Ok(state) => state,
    Err(e) if e.to_string().contains("channel closed") => {
        log::error!("worker died loading state {key}: {e:#}; retrying once");
        cache.load_state(key)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The background task spawned inside `load_state` panics before `tx.send(result)` (e.g. deserialization panic, poisoned mutex, OOM), or the task is dropped/aborted, so `rx` yields `Err(RecvError)`.

Common situations: Redis data in an unexpected format causing a panic in the loader task; async runtime shutdown during process exit; bugs in a forked/patched version of the cache where the spawned task fails silently (the original send failure is only logged, not surfaced).

Related errors


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