nautechsystems/nautilus_trader · critical

Cannot take ownership - other references exist

Error message

Cannot take ownership - other references exist

What it means

BitmexWebSocketClient::stream() consumes the single `Arc`-wrapped broadcast receiver (`out_rx`) by `take()`-ing it and then calling `Arc::try_unwrap`. The panic fires when other `Arc` clones still exist, meaning the receiver is shared and exclusive ownership cannot be obtained. The library requires that `stream()` be called exactly once and that no other holder of the receiver remains.

Source

Thrown at crates/adapters/bitmex/src/websocket/client.rs:644

            ))
        })?;

        Ok(())
    }

    /// Provides the internal stream as a channel-based stream.
    ///
    /// # Panics
    ///
    /// This function panics:
    /// - If the websocket is not connected.
    /// - If `stream` has already been called somewhere else (stream receiver is then taken).
    pub fn stream(&mut self) -> impl Stream<Item = BitmexWsMessage> + use<> {
        let rx = self
            .out_rx
            .take()
            .expect("Stream receiver already taken or not connected");
        let mut rx = Arc::try_unwrap(rx).expect("Cannot take ownership - other references exist");
        async_stream::stream! {
            while let Some(msg) = rx.recv().await {
                yield msg;
            }
        }
    }

    /// Closes the client.
    ///
    /// # Errors
    ///
    /// Returns an error if the WebSocket is not connected or if closing fails.
    pub async fn close(&mut self) -> Result<(), BitmexWsError> {
        log::debug!("Starting close process");

        self.signal.store(true, Ordering::Relaxed);

        // Send Disconnect command to handler

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Call `stream()` at most once per client connection and drop any previously obtained stream before calling again.
  2. Create a fresh BitmexWebSocketClient (or reconnect) for each independent consumer instead of sharing one receiver.
  3. If broadcast semantics to multiple consumers are needed, subscribe/broadcast upstream rather than unwrapping the single Arc.
  4. Ensure no background task still holds a clone of the receiver when `stream()` is called.

Example fix

// before
let s1 = client.stream();
let s2 = client.stream(); // panics: other references exist
// after
let s1 = client.stream(); // single consumer; drop s1 before re-streaming
// or: build a second client for the second consumer
let s2 = other_client.stream();
Defensive patterns

Strategy: validation

Validate before calling

// Call stream() exactly once per client; track with a flag
if stream_already_created {
    panic!("stream() already called; reuse the existing stream or create a new client");
}

Try / catch

// Rust panics cannot be caught except via catch_unwind; prefer
let stream = client.stream(); // keep the returned stream; never call stream() again
while let Some(msg) = stream.next().await { /* single consumer only */ }

Prevention

When it happens

Trigger: Calling `client.stream()` while another clone of the internal `Arc<Receiver<BitmexWsMessage>>` is still alive (e.g. a previously obtained stream is still running, or a clone was taken for another task).

Common situations: Calling `stream()` twice on the same client; spawning two consumers on one WebSocket client; keeping a reference to the stream alive while reconnecting/re-creating the stream.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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