nautechsystems/nautilus_trader · error

instrument update lock poisoned

Error message

instrument update lock poisoned

What it means

The OKX data engine holds a dedicated instrument-update mutex across each WebSocket instruments batch (diff, cache update, publish). This expect panics when that mutex is poisoned — some other thread panicked while holding it (e.g. inside the reconcile pass at line 1595 or request_instruments).

Source

Thrown at crates/adapters/okx/src/data.rs:693

                    ts_init,
                    funding_cache,
                    &instruments_guard,
                ) {
                    Ok(Some(ws_msg)) => {
                        dispatch_parsed_data(ws_msg, data_sender, instruments_by_symbol);
                    }
                    Ok(None) => {}
                    Err(e) => log::error!("Failed to parse {channel:?} data: {e}"),
                }
            }
            OKXWsMessage::Instruments(okx_instruments) => {
                let ts_init = clock.get_time_ns();
                // Hold the instrument lock for the batch so a concurrent
                // reconciliation cannot interleave diff, cache update, and publish
                let _update_guard = instrument_update_lock
                    .mutex
                    .lock()
                    .expect("instrument update lock poisoned");

                for okx_inst in okx_instruments {
                    let inst_key = okx_inst.inst_id;
                    let cached = instruments_by_symbol.get_cloned(&inst_key);
                    let (margin_init, margin_maint, maker_fee, taker_fee) = cached
                        .as_ref()
                        .map_or((None, None, None, None), |instrument| {
                            extract_fees_from_cached_instrument(instrument)
                        });
                    let status_action = okx_status_to_market_action(okx_inst.state);
                    let is_live = matches!(okx_inst.state, OKXInstrumentStatus::Live);
                    match parse_instrument_any(
                        &okx_inst,
                        margin_init,
                        margin_maint,
                        maker_fee,
                        taker_fee,
                        ts_init,

View on GitHub (pinned to d1527c24af)

Solutions

  1. Locate the original panic under instrument_update_lock (earlier in logs) and fix that payload/conversion bug
  2. Keep only non-panicking operations under the guard; propagate Result through the batch handler
  3. Consider `unwrap_or_else(|e| e.into_inner())` — the lock guards ordering, and data invariants may remain usable after a panic

Example fix

// before
let _update_guard = instrument_update_lock.mutex.lock().expect("instrument update lock poisoned");
// after
let _update_guard = instrument_update_lock.mutex.lock().unwrap_or_else(|e| e.into_inner());
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: handle_ws_message processing an OKX instruments channel batch after a panic occurred anywhere the same instrument_update_lock is held: reconcile_instruments, request_instruments, or request_instrument.

Common situations: A panic in instrument parsing/definition conversion under the lock (bad OKX payload edge case) poisons the lock; every subsequent WS instruments message then panics the data engine loop.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@d1527c24af (2026-08-27). Data as JSON: /api/errors/ad420a2767055329. Report an issue: GitHub.