nautechsystems/nautilus_trader · critical
Cannot take ownership - other references exist
Error message
Cannot take ownership - other references exist
What it means
Inside BybitWebSocketClient::stream(), the receiver is stored in an Arc<Receiver>; Arc::try_unwrap converts it to an owned Receiver only if this is the last reference. The expect fires when other clones of the Arc still exist (e.g. a spawn kept a handle for subscribe/order-response routing), so ownership cannot be moved into the async_stream generator.
Source
Thrown at crates/adapters/bybit/src/websocket/client.rs:951
let cmd = HandlerCommand::Unsubscribe { topics: payloads };
if let Err(e) = self.cmd_tx.read().await.send(cmd) {
log::debug!("Failed to send unsubscribe command: error={e}");
}
Ok(())
}
/// Returns a stream of venue-typed [`BybitWsMessage`] items.
///
/// # Panics
///
/// Panics if called before [`Self::connect`] or if the stream has already been taken.
pub fn stream(&mut self) -> impl futures_util::Stream<Item = BybitWsMessage> + use<> {
let rx = self
.out_rx
.take()
.expect("Stream receiver already taken or client 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;
}
}
}
/// Returns the number of currently registered subscriptions.
#[must_use]
pub fn subscription_count(&self) -> usize {
self.subscriptions.len()
}
/// Returns the credential associated with this client, if any.
#[must_use]
pub fn credential(&self) -> Option<&Credential> {
self.credential.as_ref()
}View on GitHub (pinned to 18893faf8b)
Solutions
- Drop or join all other holders of the receiver Arc before calling stream() (e.g. abort the connection task that cloned it).
- Restructure so the stream is the sole consumer and other components send messages into the client instead of cloning the receiver.
- If sharing is genuinely required, wrap messages in an Arc and use a broadcast channel rather than relying on try_unwrap.
- In tests, clone the Arc only after stream() has been called, or use try_recv on the owned receiver.
Example fix
// before let rx_clone = std::mem::discriminant(&client); // some task still holds out_rx Arc let stream = client.stream(); // panics: other Arc references exist // after drop(reader_task_handle); // or reader_task_handle.await; release the clone first let stream = client.stream();
Defensive patterns
Strategy: validation
Validate before calling
// Ensure no clones of the receiver exist before taking the stream: // join or abort tasks that cloned out_rx, then call stream(). assert!(reader_task.is_finished(), "receiver holder must be done before stream()");
Try / catch
// Unreachable-safety net only: std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| client.stream()))
Prevention
- Do not clone the out_rx Arc; treat the stream as the single consumer.
- Drop/join reader tasks before calling stream().
- Use broadcast channels when multiple consumers are required.
When it happens
Trigger: Calling stream() while some component (connection task, test harness, subscription router) still holds a clone of the out_rx Arc.
Common situations: Tests like test_trade_order_response_preserves_request_id that clone the receiver to assert on responses; refactors that retain an Arc handle for sending requests while also draining via stream(); failing to shut down the reader task before taking the stream.
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
- Cannot take ownership of stream - client was cloned and othe
- Cannot take ownership of stream - client was cloned and othe
- Stream receiver already taken or client not connected
- InstrumentState channel requires kind and currency parameter
- funding_interval_hour out of bounds
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/9294b30f33b55b9b.
Report an issue: GitHub.