nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send message: {e}

Error message

Failed to send message: {e}

What it means

The WebSocket handler could not deliver a text message (subscribe/unsubscribe control frame) through the active Binance Spot stream connection. `send_text` on the inner client failed with the underlying error `{e}` (connection closed, rate limiter rejection, backpressure, etc.).

Source

Thrown at crates/adapters/binance/src/spot/websocket/streams/handler.rs:297

            self.pending_requests.take(request_id);
            return Err(e);
        }

        Ok(())
    }

    async fn send_text(
        &self,
        payload: String,
        rate_limit_keys: Option<&[Ustr]>,
    ) -> anyhow::Result<()> {
        let Some(client) = &self.inner else {
            anyhow::bail!("No active WebSocket client");
        };
        client
            .send_text(payload, rate_limit_keys)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to send message: {e}"))?;
        Ok(())
    }
}

/// Classifies a JSON text frame that did not match a subscription response or
/// known error envelope. Recognizes the `serverShutdown` event; otherwise
/// emits `RawJson` for parseable payloads or an empty vector for garbage.
fn classify_unsolicited_json(text: &str) -> Vec<BinanceSpotWsMessage> {
    let Ok(value) = serde_json::from_str::<serde_json::Value>(text) else {
        log::warn!("Failed to parse JSON message: {text}");
        return vec![];
    };

    if value.get("e").and_then(|v| v.as_str()) == Some("serverShutdown")
        && let Ok(msg) = serde_json::from_value::<BinanceSpotServerShutdownMsg>(value.clone())
    {
        log::warn!(
            "Binance server shutdown notice received (event_time={}); disconnect expected ~10 minutes from event",

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the underlying `{e}`: for a closed connection, reconnect the stream client and re-issue the subscription.
  2. Verify rate-limit configuration (`rate_limit_keys`) — reduce subscription churn or increase limits if the send was throttled.
  3. Ensure the WebSocket connection is established and healthy before calling subscribe/unsubscribe.
  4. Add retry/backoff around send for transient errors and monitor the stream's reconnect lifecycle.

Example fix

// before: fire-and-forget subscribe, error aborts handler
client.send_text(payload, keys).await?;
// after: tolerate transient failure, allow reconnect logic to resubscribe
match client.send_text(payload, keys).await {
    Ok(()) => Ok(()),
    Err(e) => { warn!("send failed, will resubscribe after reconnect: {e}"); Ok(()) }
}
Defensive patterns

Strategy: retry

Validate before calling

if stream_client.connection_state() != ConnectionState::Active {
    return Err(anyhow::anyhow!("cannot send: websocket not active"));
}

Try / catch

match client.send_text(payload, keys).await {
    Ok(()) => Ok(()),
    Err(e) if is_connection_closed(&e) => {
        stream_client.reconnect().await?;
        client.send_text(payload, keys).await
            .map_err(|e| anyhow::anyhow!("Failed to send message: {e}"))
    }
    Err(e) => Err(anyhow::anyhow!("Failed to send message: {e}")),
}

Prevention

When it happens

Trigger: `handle_subscribe` or `handle_unsubscribe` builds a subscription payload and calls `send_text` while the connection is being closed or saturated; the inner `client.send_text(payload, rate_limit_keys).await` returns `Err(e)` which is wrapped into this error.

Common situations: WebSocket dropped mid-session (network blip, server shutdown) and a subscribe is still pending; exceeding Binance message rate limits; trying to send before the connection handshake finished despite `inner` being set.

Related errors


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