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.
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
- Execution event sender should be initialized by runner
- System command sender should be initialized by runner
- 16 lowercase hex bytes are valid TradeId
- AccountId contains '-'
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/2302b1f7ab905a9e.
Report an issue: GitHub.
Appendix: 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)