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
- Initialize via the live runner (which binds SYSTEM_EVENT_SENDER) before any consumer calls this
- Only call on the runner's thread; otherwise use `try_get_system_event_sender()` and handle the None case
- Pass the sender explicitly through constructors where cross-thread access is needed
- 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
- Bind runner channels before any component publishes system events
- Keep publishing code on the runner's thread; pass senders explicitly for cross-thread use
- Use try_get_* variant in startup paths with graceful failure
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
- Data event sender should be initialized by runner
- System command sender should be initialized by runner
- Execution event sender should be initialized by runner
- in-flight mutex poisoned
- wallet balance mutex poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/2302b1f7ab905a9e.
Report an issue: GitHub.