nautechsystems/nautilus_trader · error

Invalid state trigger

Error message

Invalid state trigger {self} -> {trigger}

What it means

ComponentState::transition implements an explicit finite state machine for component lifecycles (PreInitialized, Ready, Starting, Running, Stopping, Degraded, Faulting, Disposing, etc.). Any (state, trigger) pair not in the table is invalid — e.g. triggering Fault on a Running component without going through Stopping first — and the call bails. This protects components from illegal lifecycle jumps.

Solutions

  1. Inspect the component's current state (component.state()) before issuing a transition trigger and only send triggers with a valid edge from that state.
  2. Use the higher-level helpers (stop_component, dispose_component, reset_component) instead of raw transition calls; they handle state checks.
  3. Serialize lifecycle calls — do not trigger transitions concurrently from multiple tasks/threads.
  4. Check prior error logs: an earlier fault may have moved the component into Faulted/Faulting, making subsequent triggers invalid; reset or dispose from there.

Example fix

// before
if let Err(e) = component.transition(ComponentTrigger::Fault) { /* Fault from Running is invalid */ }
// after
let next = component.state().transition(ComponentTrigger::Stop); // or use stop_component helper
if next.is_ok() {
    component.transition(ComponentTrigger::Stop);
} else {
    log::warn!("Skipping transition: {:?}", next.err());
}
Defensive patterns

Strategy: try-catch

Validate before calling

fn can_transition(state: ComponentState, trigger: ComponentTrigger) -> bool {
    state.transition(trigger).is_ok()
}

Try / catch

match component.transition(trigger) {
    Ok(new_state) => log::debug!("transitioned to {new_state}"),
    Err(e) if e.to_string().starts_with("Invalid state trigger") => {
        log::warn!("ignoring invalid lifecycle trigger: {e}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling component transition APIs (stop/fault/dispose) in an order the state machine does not allow, e.g. sending ComponentTrigger::Fault while the component is in a state with no Fault edge, or double-triggering a transition (stop twice, dispose twice).

Common situations: Race conditions where two lifecycle calls interleave (a stop callback firing while fault handling is in progress); retrying a failed stop and hitting an unexpected state; custom actors/strategies overriding callbacks and triggering transitions manually; shutdown sequences disposing components that already faulted.

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

Appendix: source

Thrown at crates/common/src/component.rs:402

            (Self::Resuming, ComponentTrigger::Stop) => Self::Stopping,
            (Self::Resuming, ComponentTrigger::ResumeCompleted) => Self::Running,
            (Self::Resuming, ComponentTrigger::Fault) => Self::Faulting,
            (Self::Stopping, ComponentTrigger::StopCompleted) => Self::Stopped,
            (Self::Stopping, ComponentTrigger::Dispose) => Self::Disposing,
            (Self::Stopping, ComponentTrigger::Fault) => Self::Faulting,
            (Self::Stopped, ComponentTrigger::Reset) => Self::Resetting,
            (Self::Stopped, ComponentTrigger::Resume) => Self::Resuming,
            (Self::Stopped, ComponentTrigger::Dispose) => Self::Disposing,
            (Self::Stopped, ComponentTrigger::Fault) => Self::Faulting,
            (Self::Degrading, ComponentTrigger::DegradeCompleted) => Self::Degraded,
            (Self::Degraded, ComponentTrigger::Resume) => Self::Resuming,
            (Self::Degraded, ComponentTrigger::Stop) => Self::Stopping,
            (Self::Degraded, ComponentTrigger::Fault) => Self::Faulting,
            (Self::Disposing, ComponentTrigger::DisposeCompleted) => Self::Disposed,
            (Self::Disposing, ComponentTrigger::Fault) => Self::Faulting,
            (Self::Faulting, ComponentTrigger::Dispose) => Self::Disposing,
            (Self::Faulting, ComponentTrigger::FaultCompleted) => Self::Faulted,
            _ => anyhow::bail!("Invalid state trigger {self} -> {trigger}"),
        };
        Ok(new_state)
    }
}

thread_local! {
    static COMPONENT_REGISTRY: ComponentRegistry = ComponentRegistry::new();
}

/// Registry for storing components with runtime borrow tracking.
///
/// The registry tracks which components are currently mutably borrowed to prevent
/// multiple simultaneous mutable borrows (which would be undefined behavior).
pub struct ComponentRegistry {
    components: RefCell<AHashMap<Ustr, Rc<UnsafeCell<dyn Component>>>>,
    borrows: RefCell<AHashSet<Ustr>>,
}

View on GitHub (pinned to 18893faf8b)