nautechsystems/nautilus_trader · error

Cannot take ownership of stream - client was cloned and othe

Error message

Cannot take ownership of stream - client was cloned and other references exist

What it means

In AxOrdersWebSocketClient::stream(), the shared receiver is an Arc and stream() requires unique ownership (Arc::try_unwrap) to return a 'static stream. If clones of the client still exist, try_unwrap returns Err and this expect panics, protecting the single-consumer contract of the orders stream.

Source

Thrown at crates/adapters/architect_ax/src/websocket/orders/client.rs:703

        self.send_cmd(HandlerCommand::GetOpenOrders { request_id })
            .await?;

        Ok(request_id)
    }

    /// Returns a stream of WebSocket messages.
    ///
    /// # Panics
    ///
    /// Panics if called before `connect()` or if the stream has already been taken.
    pub fn stream(&mut self) -> impl futures_util::Stream<Item = AxOrdersWsMessage> + 'static {
        let rx = self
            .out_rx
            .take()
            .expect("Stream receiver already taken or client not connected - stream() can only be called once");
        let mut rx = Arc::try_unwrap(rx).expect(
            "Cannot take ownership of stream - client was cloned and other references exist",
        );
        async_stream::stream! {
            while let Some(msg) = rx.recv().await {
                yield msg;
            }
        }
    }

    pub(crate) fn begin_shutdown(&self) {
        self.cancellation_token.load().cancel();
        self.signal.store(true, Ordering::Release);
    }

    /// Disconnects the WebSocket connection gracefully.
    pub async fn disconnect(&self) {
        log::debug!("Disconnecting WebSocket");
        let _ = self.send_cmd(HandlerCommand::Disconnect).await;
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Drop all clones before calling stream(), or call stream() only on the final remaining handle.
  2. Dedicate one handle exclusively to streaming and use clones only for send operations.
  3. Fan out messages yourself: consume the stream in one task and forward to per-consumer channels.
  4. If the design truly needs concurrent receivers, switch to a broadcast channel in your wrapper layer.

Example fix

// before
let clone = client.clone();
send_orders(clone); // clone moved into task but another clone retained
let stream = client.stream(); // panics: references exist
// after
// keep exactly one handle for streaming
let stream = client.stream();
// derive senders from a separate connection or drop clones first
Defensive patterns

Strategy: validation

Validate before calling

// Ensure no clones of the orders client are alive before streaming
// (avoid .clone() on the client intended for stream(); keep one unique handle)

Prevention

When it happens

Trigger: Calling stream() while any clone of the AxOrdersWebSocketClient (created with .clone()) is still alive, e.g. a clone held by an order-sending task or a long-lived actor.

Common situations: Sharing one client between an order manager and a WebSocket event loop; holding a clone in application state while the stream is consumed elsewhere; tests cloning the client to inspect state before streaming.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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