nautechsystems/nautilus_trader · error

Data event sender should be initialized by runner

Error message

Data event sender should be initialized by runner

What it means

This panic comes from `get_data_event_sender()` in crates/common/src/live/runner.rs. The function reads a thread-local `DATA_EVENT_SENDER` slot that the live runner must initialize before any consumer fetches it; if the slot is still empty, the expect panics with this message. It exists so components on the live thread can obtain a channel sender for DataEvents without threading it through every constructor.

Source

Thrown at crates/common/src/live/runner.rs:35

//!
//! This module provides thread-local storage for tokio mpsc channels used in live trading.

use std::cell::RefCell;

use crate::messages::{DataEvent, ExecutionEvent, SystemCommand, SystemEvent};

/// Gets the thread-local data event sender.
///
/// # Panics
///
/// Panics if the sender is uninitialized.
#[must_use]
pub fn get_data_event_sender() -> tokio::sync::mpsc::UnboundedSender<DataEvent> {
    DATA_EVENT_SENDER.with(|sender| {
        sender
            .borrow()
            .as_ref()
            .expect("Data event sender should be initialized by runner")
            .clone()
    })
}

/// Attempts to get the thread-local data event sender without panicking.
///
/// Returns `None` if the sender is not initialized (e.g., in Python/v1 bridge environments
/// before a runner or adapter bridge has registered a sender).
#[must_use]
pub fn try_get_data_event_sender() -> Option<tokio::sync::mpsc::UnboundedSender<DataEvent>> {
    DATA_EVENT_SENDER.with(|sender| sender.borrow().as_ref().cloned())
}

/// Sets the thread-local data event sender.
///
/// Can only be called once per thread.
///
/// # Panics

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the live runner initializes the sender on the same thread before components call this function
  2. Call this only from the runner's thread (it is thread-local); use `try_get_data_event_sender()` (the non-panicking variant) when unsure and handle None
  3. Restructure initialization so senders are passed explicitly to components instead of fetched from thread-local state
  4. In tests, initialize the thread-local sender via the runner harness before calling

Example fix

// before
let sender = get_data_event_sender(); // panics on uninitialized thread
// after
let sender = match try_get_data_event_sender() {
    Some(s) => s,
    None => anyhow::bail!("data event sender not initialized; call from the live runner thread after initialization"),
};
Defensive patterns

Strategy: fallback

Validate before calling

assert!(try_get_data_event_sender().is_some(), "call from live runner thread after init");

Type guard

fn sender_ready() -> bool { try_get_data_event_sender().is_some() }

Try / catch

// no exception to catch in Rust; use the non-panicking variant
let sender = try_get_data_event_sender().ok_or_else(|| anyhow!("data event sender not initialized"))?;

Prevention

When it happens

Trigger: Calling `get_data_event_sender()` on a thread where the live runner never ran its initialization (i.e. `DATA_EVENT_SENDER` was never set) — e.g. calling from a different thread than the runner's, calling before `run`/`connect` initializes it, or in unit tests without the runner harness.

Common situations: Spawning actor/strategy code on a fresh tokio task or OS thread separate from the runner thread and then grabbing the sender; constructing components in tests without using the runner's initialization path; early startup ordering where a component fetches the sender before the runner sets it.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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