nautechsystems/nautilus_trader · error

Cannot register OMS type: {e}

Error message

Cannot register OMS type: {e}

What it means

This error wraps a `RefCell::try_borrow_mut` failure on the live node's kernel exec engine. The kernel stores `exec_engine` in a `RefCell` to allow shared access across the node; if the engine is already mutably (or even immutably) borrowed by another call on the same thread, `try_borrow_mut` returns `Err` and the node raises this message. It is a re-entrancy / aliasing violation, not a domain problem with the OMS type itself.

Source

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

        // Capture strategy-owned values before adding the strategy, which moves it
        let strategy_id = self
            .kernel
            .trader
            .borrow()
            .prepare_strategy_for_registration(&mut strategy)?;
        let oms_type = StrategyNative::strategy_core(&strategy).config.oms_type;
        let instrument_ids = strategy.external_order_instrument_ids().unwrap_or_default();

        if !instrument_ids.is_empty() {
            self.register_external_order_claims(strategy_id, &instrument_ids)?;
        }

        let mut exec_engine = match oms_type
            .map(|_| {
                self.kernel
                    .exec_engine
                    .try_borrow_mut()
                    .map_err(|e| anyhow::anyhow!("Cannot register OMS type: {e}"))
            })
            .transpose()
        {
            Ok(exec_engine) => exec_engine,
            Err(e) => {
                if !instrument_ids.is_empty()
                    && let Err(rollback_error) =
                        self.rollback_external_order_claims(strategy_id, &instrument_ids)
                {
                    anyhow::bail!(
                        "{e}; failed to roll back external order claims for {strategy_id}: {rollback_error}"
                    );
                }
                return Err(e);
            }
        };

        if let Err(add_error) = self.kernel.trader.borrow_mut().add_strategy(strategy) {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure no other borrow of the kernel exec engine is alive on the current thread when calling `add_strategy` (drop `borrow()`/`borrow_mut()` guards first).
  2. Move strategy registration to node startup, before the trading loop/event callbacks begin.
  3. If calling from a callback, defer registration (queue it and register from the outer scope).
  4. Inspect the `e` in the message: Pyo3 borrows print the location of the conflicting borrow, which identifies the holder.

Example fix

// before
let engine = node.kernel.exec_engine.borrow();
node.add_strategy(strategy, ...)?;
// after
drop(node.kernel.exec_engine.borrow()); // release before registering
node.add_strategy(strategy, ...)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Check the exec engine borrow is free before registering
if node.kernel.exec_engine.try_borrow().is_err() {
    return Err(anyhow::anyhow!("exec engine already borrowed; defer add_strategy"));
}

Try / catch

match node.add_strategy(strategy, instrument_ids) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Cannot register OMS type") => {
        // retry after borrows are released / defer to startup phase
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `add_strategy` while the kernel's `exec_engine` RefCell is already borrowed — e.g. calling `add_strategy` from inside a callback that itself holds a borrow of the exec engine, or calling it while a previous registration on the same thread is still in scope.

Common situations: Registering a strategy from within an actor/event handler on the live node; nested `add_strategy` calls in async code where a previous borrow hasn't been dropped; holding `node.kernel.exec_engine.borrow()` in user code before calling `add_strategy`.

Related errors


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