{"record":{"id":"f6a5bd050cf99c89","repo":"nautechsystems/nautilus_trader","slug":"in-flight-mutex-poisoned","errorCode":null,"errorMessage":"in-flight mutex poisoned","messagePattern":"in-flight mutex poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":159,"sourceCode":"            \"A {} transaction is being prepared; at most one transaction can be in flight\",\n            purpose.as_str()\n        ),\n        InFlightSlot::AwaitingFinality(in_flight) => anyhow::anyhow!(\n            \"Transaction {} (intent {}, {}, nonce {}) is still awaiting finality; at most one transaction can be in flight\",\n            in_flight.tx_hash,\n            in_flight.intent_id,\n            in_flight.purpose.as_str(),\n            in_flight.nonce\n        ),\n    }\n}\n\n/// Releases a pre-signature slot claim when the slot is still in the preparing state.\n///\n/// Aborted or failed preparation can leave a claim behind; because no signed transaction\n/// exists for a preparing slot, releasing it cannot strand a broadcastable signature.\nfn release_preparing_slot(in_flight: &Mutex<Option<InFlightSlot>>) {\n    let mut slot = in_flight.lock().expect(\"in-flight mutex poisoned\");\n    if matches!(*slot, Some(InFlightSlot::Preparing(_))) {\n        *slot = None;\n    }\n}\n\n#[derive(Debug)]\nstruct TransactionLimits {\n    allowed_token_pairs: HashSet<(Address, Address)>,\n    slippage_bps: u32,\n    max_slippage_bps: u32,\n    max_order_amount: u64,\n    deadline_seconds: u64,\n    max_quote_age_blocks: u64,\n    receipt_timeout_secs: u64,\n}\n\n/// Execution client for blockchain interactions including balance tracking and order execution.\n#[derive(Debug)]","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/execution/client.rs#L141-L177","documentation":"release_preparing_slot locks the blockchain execution client's in_flight mutex (std::sync::Mutex<Option<InFlightSlot>>) to clear a Preparing claim left behind by an aborted or failed pre-signature preparation; because no signed transaction exists for a preparing slot, releasing it cannot strand a broadcastable signature. The .expect panics when the mutex is poisoned, which in Rust means an earlier panic unwound through a critical section of this same mutex. Poisoning is permanent for the process lifetime, so the panic repeats on every later attempt to release, claim, or inspect the slot.","triggerScenarios":"A prepare path aborts or fails after claim_slot and calls release_preparing_slot while the in_flight mutex was already poisoned by an earlier panic in any critical section: claim_slot, ensure_transaction_ready, fill_and_persist, release_slot, or the startup restore.","commonSituations":"An earlier unwrap/index/expect panic elsewhere in the execution client was logged but a task supervisor kept the process running; a custom fork added a panicking conversion inside a critical section; a version upgrade shipped a panic bug in the signing or preparation pipeline.","solutions":["Hunt the original panic: search the log above this message for the FIRST panic in the process; a poisoned mutex is always a secondary symptom of that earlier unwind while the lock was held.","Restart the process: a poisoned std::sync::Mutex never recovers in-process, and this client rebuilds in-flight state from its durable store on startup (intent, current transaction hash, nonce), so restart is the safe recovery for a capital-touching component.","If you maintain this code and can prove the guarded state is consistent, replace .expect with .lock().unwrap_or_else(|e| e.into_inner()); for live trading prefer a restart over blind recovery.","Audit every critical section of this mutex so it contains only infallible moves (assignment, retain, clone) with no RPCs, awaits, or panicking conversions, so future bugs cannot poison it."],"exampleFix":"// before\nlet mut slot = in_flight.lock().expect(\"in-flight mutex poisoned\");\n\n// after (recover the inner state; prefer a process restart for live trading)\nlet mut slot = in_flight.lock().unwrap_or_else(|e| e.into_inner());","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let handle = tokio::spawn(execution_client_task);\nif let Err(join_err) = handle.await {\n    if join_err.is_panic() {\n        // mutex poison: alert, then restart the process so the durable store\n        // restores in-flight state on startup\n        restart_execution_host();\n    }\n}","preventionTips":["Treat any panic in a process holding shared trading state as fatal: alert and restart instead of logging and continuing.","Search for the first panic above the poison message in logs; poison is always a secondary failure.","Never hold a std::sync::Mutex guard across an .await point; scope locks to infallible data moves only.","Supervise the execution client task so a panic triggers a clean restart; startup restores the in-flight slot from the durable store."],"tags":["rust","mutex","panic","blockchain","execution","nautilustrader"],"backgroundTag":"mutex-poisoned","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}