nautechsystems/nautilus_trader · error

System event sender should be initialized by runner

Error message

System event sender should be initialized by runner

What it means

This panic comes from `get_system_event_sender()` in crates/common/src/live/runner.rs. It reads the thread-local `SYSTEM_EVENT_SENDER` slot that the live runner is expected to initialize; if it has not been set on the current thread, the expect panics. The sender is used to publish SystemEvents (e.g. state changes) from components to the runner's event loop.

Source

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

/// Replaces the data event sender for the current thread.
pub fn replace_data_event_sender(sender: tokio::sync::mpsc::UnboundedSender<DataEvent>) {
    DATA_EVENT_SENDER.with(|s| {
        *s.borrow_mut() = Some(sender);
    });
}

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

/// Attempts to get the thread-local system event sender without panicking.
///
/// Returns `None` if the sender is not initialized (e.g., in test environments).
#[must_use]
pub fn try_get_system_event_sender() -> Option<tokio::sync::mpsc::UnboundedSender<SystemEvent>> {
    SYSTEM_EVENT_SENDER.with(|sender| sender.borrow().as_ref().cloned())
}

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

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Initialize via the live runner (which binds SYSTEM_EVENT_SENDER) before any consumer calls this
  2. Only call on the runner's thread; otherwise use `try_get_system_event_sender()` and handle the None case
  3. Pass the sender explicitly through constructors where cross-thread access is needed
  4. In tests, replicate the runner's channel binding before invoking code paths that publish system events

Example fix

// before
let sender = get_system_event_sender(); // panics if runner not initialized
// after
let Some(sender) = try_get_system_event_sender() else {
    anyhow::bail!("system event sender missing; ensure live runner initialized this thread");
};
Defensive patterns

Strategy: fallback

Validate before calling

assert!(try_get_system_event_sender().is_some(), "system sender must be bound by runner first");

Type guard

fn system_sender_ready() -> bool { try_get_system_event_sender().is_some() }

Try / catch

let sender = try_get_system_event_sender().ok_or_else(|| anyhow!("system event sender not initialized"))?;

Prevention

When it happens

Trigger: Calling `get_system_event_sender()` before the runner initialized the thread-local, from a different thread than the runner (thread-local miss), or in tests that exercise components without binding the runner channels.

Common situations: Components fetching the system sender during startup before `connect`/`bind_senders` ran; actor code moved to another tokio worker thread where the thread-local is unset; test harnesses calling publish paths without the runner's channel binding step.

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/2302b1f7ab905a9e. Report an issue: GitHub.