nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send UnsubscribeMarket: {e}

Error message

Failed to send UnsubscribeMarket: {e}

What it means

unsubscribe_market() enqueues HandlerCommand::UnsubscribeMarket on the internal mpsc channel to the feed-handler task. It fails only when the receiver has been dropped because the handler task has exited, meaning there is no active session to process the unsubscribe.

Source

Thrown at crates/adapters/polymarket/src/websocket/client.rs:87

}

impl WsSubscriptionHandle {
    /// Sends a market subscribe command to the handler.
    pub async fn subscribe_market(&self, asset_ids: Vec<String>) -> anyhow::Result<()> {
        self.cmd_tx
            .read()
            .await
            .send(HandlerCommand::SubscribeMarket(asset_ids))
            .map_err(|e| anyhow::anyhow!("Failed to send SubscribeMarket: {e}"))
    }

    /// Sends a market unsubscribe command to the handler.
    pub async fn unsubscribe_market(&self, asset_ids: Vec<String>) -> anyhow::Result<()> {
        self.cmd_tx
            .read()
            .await
            .send(HandlerCommand::UnsubscribeMarket(asset_ids))
            .map_err(|e| anyhow::anyhow!("Failed to send UnsubscribeMarket: {e}"))
    }

    // Constructs a handle around a raw command sender. Test-only: lets unit
    // tests observe the commands the handle emits without spinning up the real
    // feed handler.
    #[cfg(test)]
    pub(crate) fn from_sender(sender: tokio::sync::mpsc::UnboundedSender<HandlerCommand>) -> Self {
        Self {
            cmd_tx: Arc::new(tokio::sync::RwLock::new(sender)),
        }
    }
}

/// Provides a WebSocket client for the Polymarket CLOB API.
///
/// A single instance targets one channel (market or user). Use
/// [`PolymarketWebSocketClient::new_market`] for public market data and
/// [`PolymarketWebSocketClient::new_user`] for authenticated order/trade streams.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Guard the call with a connected/is_active check
  2. If the client is down, the subscription is already gone — skip unsubscribe_market() and return Ok
  3. Call connect() first if you need the client live again, then unsubscribe
  4. Investigate why the handler task exited via its logs

Example fix

// before
client.unsubscribe_market(asset_ids).await?;
// after
if client.is_active() {
    client.unsubscribe_market(asset_ids).await?;
} // else: subscription died with the session, nothing to unsubscribe
Defensive patterns

Strategy: try-catch

Validate before calling

if client.is_active() {
    client.unsubscribe_market(asset_ids).await?;
}

Try / catch

if let Err(e) = client.unsubscribe_market(asset_ids).await {
    // handler gone means the venue subscription is gone too; safe to ignore on shutdown
    log::debug!("unsubscribe skipped, handler not running: {e:#}");
}

Prevention

When it happens

Trigger: Calling unsubscribe_market() after disconnect(), after the handler task terminated (panic or fatal connection error), or concurrently with a reconnect that replaces the handler task.

Common situations: Cleanup/shutdown code unsubscribing after the client was already disconnected; handler died earlier from a network outage; test harness dropping the client while an unsubscribe is in flight.

Related errors


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