nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send SubscribeMarket: {e}

Error message

Failed to send SubscribeMarket: {e}

What it means

subscribe_market() enqueues a HandlerCommand::SubscribeMarket on the internal mpsc channel consumed by the background feed-handler task. The send only fails when the channel receiver has been dropped, i.e. the handler task has exited, so there is no live WebSocket session to receive the subscription. This converts the tokio SendError into an anyhow error for the caller.

Source

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

    User,
}

/// Lightweight handle for subscribing/unsubscribing to market data.
///
/// `Clone` + `Send` safe for use in spawned async tasks.
#[derive(Clone, Debug)]
pub struct WsSubscriptionHandle {
    cmd_tx: Arc<tokio::sync::RwLock<tokio::sync::mpsc::UnboundedSender<HandlerCommand>>>,
}

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)),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the client is connected (e.g. is_active()) before calling subscribe_market()
  2. Call connect() again to respawn the handler task, then retry subscribe_market()
  3. Remove the race between disconnect() and subscribe_market() (e.g. hold a lock or route all commands through one task)
  4. Check logs from the handler task to find why it exited and fix the root cause

Example fix

// before
client.subscribe_market(asset_ids).await?; // panics/errors if handler stopped
// after
if !client.is_active() {
    client.connect().await?;
}
client.subscribe_market(asset_ids).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust
if !client.is_active() {
    client.connect().await?;
}

Try / catch

match client.subscribe_market(asset_ids).await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Failed to send SubscribeMarket") => {
        client.connect().await?; // handler gone: respawn and retry once
        client.subscribe_market(asset_ids).await?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling subscribe_market() after disconnect(), before the first successful connect(), after the handler task panicked or returned an error (e.g. repeated connection failures killed it), or racing an in-flight disconnect from another task.

Common situations: A task subscribes while another task is disconnecting/reconnecting the client; the handler task died because the gateway endpoint was unreachable and was never restarted; forgetting that PolymarketWebSocketClient is not usable between disconnect() and connect().

Related errors


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