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
- Locate the original panic under instrument_update_lock (earlier in logs) and fix that payload/conversion bug
- Keep only non-panicking operations under the guard; propagate Result through the batch handler
- 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
- Never panic inside the instruments batch handler; return Result and skip malformed instruments
- Keep publish/cache work under the lock minimal and infallible
- Recover via into_inner() plus write_seq staleness checks
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
- option_summary_family_subs mutex poisoned
- rate limiter decision lock poisoned
- Conditional order types must use OKXAlgoOrderType
- Invalid `OrderType` cannot be represented on OKX: {value:?}
- Invalid OKX websocket channel
AI-assisted analysis of nautechsystems/nautilus_trader@d1527c24af (2026-08-27).
Data as JSON: /api/errors/ad420a2767055329.
Report an issue: GitHub.