nautechsystems/nautilus_trader · critical

OCM state lock poisoned

Error message

OCM state lock poisoned

What it means

ocm_state is a std::sync::Mutex<OcmState> shared by the OCM (order change message) reconciliation path; lock() returned a PoisonError, meaning some thread panicked while holding this mutex. From that point every OCM poll (fetch_fill_reports_via_http) fails with this message until the process restarts. The real defect is the earlier panic in the logs, not this error.

Source

Thrown at crates/adapters/betfair/src/execution.rs:3038

        };

        let response =
            list_current_orders_with_retry(http_client, &params, stream_session, session_refresh)
                .await?;
        let page_size = response.current_orders.len() as u32;

        orders.extend(response.current_orders);

        if !response.more_available {
            break;
        }

        from_record += page_size;
    }

    let mut state = ocm_state
        .lock()
        .map_err(|_| anyhow::anyhow!("OCM state lock poisoned"))?;
    Ok(build_incremental_fill_reports(
        &orders, &mut state, account_id, currency, ts_init,
    ))
}

fn build_incremental_fill_reports(
    orders: &[CurrentOrderSummary],
    state: &mut OcmState,
    account_id: AccountId,
    currency: Currency,
    ts_init: UnixNanos,
) -> Vec<FillReport> {
    let mut reports = Vec::new();

    for order in orders {
        let size_matched = order.size_matched.unwrap_or(Decimal::ZERO);
        let size_voided = order.size_voided.unwrap_or(Decimal::ZERO);
        let gross_matched = size_matched + size_voided;

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Search logs backwards from this error for the originating panic and its backtrace; fix or report that panic.
  2. Restart the trading node — a poisoned std Mutex never recovers in-process.
  3. If reproducible, capture the CurrentOrderSummary/stream update that triggered the panic and file an issue with the adapter maintainers.
Defensive patterns

Strategy: try-catch

Try / catch

match reports_result {
    Ok(reports) => { /* process */ }
    Err(e) if e.to_string().contains("lock poisoned") => {
        log::error!("OCM state poisoned by earlier panic; restart required");
        // halt trading safely: cancel open orders if possible, then exit
        std::process::exit(1);
    }
    Err(e) => log::warn!("OCM poll failed: {e}"),
}

Prevention

When it happens

Trigger: Any panic inside a critical section holding ocm_state (fill-report building, customer_order_ref registration in the submit paths) poisons the lock; a subsequent generate_fill_reports call then hits the poisoned lock at execution.rs:3038.

Common situations: An upstream panic (decimal parse, unwrap, index) during order reconciliation masks itself as 'lock poisoned' on all later OCM polls; common after an unexpected venue payload triggers a panic in the parsing path.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/1bfda39d8d7c2368. Report an issue: GitHub.