nautechsystems/nautilus_trader · error

Already running

Error message

Already running

What it means

start() was called on a LiveActor/TradingNode whose kernel is already running; the node guards against double startup and returns this error instead of re-initializing clients, reconciliation, and the trader.

Solutions

  1. Check node.state().is_running() before calling start(), and skip if already running.
  2. Call start() only once per node lifecycle; use the returned handle for control operations.
  3. Stop the node first (if a restart is intended) before starting again.
  4. Serialize start calls with a mutex/once-flag if multiple tasks may trigger them.

Example fix

// before
node.start().await?;
// later...
node.start().await?; // panics-free bail: Already running
// after
if !node.state().is_running() {
    node.start().await?;
}
Defensive patterns

Strategy: validation

Validate before calling

if node.state().is_running() { /* skip start */ } else { node.start().await?; }

Type guard

fn can_start(node: &LiveNode) -> bool { !node.state().is_running() }

Try / catch

if let Err(e) = node.start().await { if e.to_string() == "Already running" { /* treat as success / no-op */ } else { return Err(e); } }

Prevention

When it happens

Trigger: Calling node.start() (or awaiting it) while the node state is already Running — e.g. start() called twice, or start() after run() has already started the node.

Common situations: Concurrent/reentrant start calls in async code without a guard; a retry wrapper that re-invokes start after a partial startup; calling start() manually after run() was already invoked.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at crates/live/src/node/mod.rs:392

        F: Fn(&dyn Any, &serde_json::Value) -> anyhow::Result<()> + 'static,
    {
        self.stream_processors
            .push(StreamProcessor(Box::new(callback)));
    }

    /// Starts the live node without entering a select loop.
    ///
    /// Connects clients, runs reconciliation, and starts the trader, but does
    /// not consume the runner or drive channel receivers, so channel traffic arriving after
    /// startup is never serviced. This is a building block for tests and embedding, not a
    /// lifecycle: use [`run`](Self::run) or [`run_with_mode`](Self::run_with_mode) to run a node.
    ///
    /// # Errors
    ///
    /// Returns an error if startup fails.
    pub async fn start(&mut self) -> anyhow::Result<()> {
        if self.state().is_running() {
            anyhow::bail!("Already running");
        }

        if self.external_msgbus.is_some() {
            log::warn!(
                "External message bus ingress is configured but LiveNode::start() does not service it; use LiveNode::run()"
            );
        }

        self.prepare_cache().await?;

        if let Some(runner) = self.runner.as_ref() {
            runner.bind_senders();
        }

        self.handle.set_starting();

        self.kernel.reset_shutdown_flag();
        self.kernel.start_async().await;

View on GitHub (pinned to 18893faf8b)