nautechsystems/nautilus_trader · error · anyhow::Error

Socket suffix cannot be empty: suffix is required for messag

Error message

Socket suffix cannot be empty: suffix is required for message framing

What it means

SocketClient requires config.suffix to be a non-empty string because the suffix is appended to messages for framing; an empty suffix would cause the read loop's windows(0) to panic. connect_url validates this first, before config.validate(), since adapters may construct the config by struct literal and bypass builder checks.

Source

Thrown at crates/network/src/socket/client.rs:124

    reconnect_attempt_count: u32,
    state_sink: Option<SocketStateSink>,
}

impl SocketClientInner {
    /// Connects to a URL with the specified configuration.
    ///
    /// # Errors
    ///
    /// Returns an error if connection fails or configuration is invalid.
    async fn connect_url(
        config: SocketConfig,
        state_sink: Option<SocketStateSink>,
    ) -> anyhow::Result<Self> {
        install_cryptographic_provider();

        // Validate suffix is non-empty to prevent panic in read loop (windows(0) panics)
        if config.suffix.is_empty() {
            anyhow::bail!("Socket suffix cannot be empty: suffix is required for message framing");
        }

        // Adapters build this config by struct literal, bypassing the builder, so this is the only
        // place the field invariants are enforced for them.
        config.validate()?;

        let connect_timeout = Duration::from_millis(config.connect_timeout_ms.unwrap_or(10_000));
        let reconnect_backoff = ExponentialBackoff::new(
            Duration::from_millis(config.reconnect_delay_initial_ms.unwrap_or(2_000)),
            Duration::from_millis(config.reconnect_delay_max_ms.unwrap_or(30_000)),
            config.reconnect_backoff_factor.unwrap_or(1.5),
            config.reconnect_jitter_ms.unwrap_or(100),
            true, // immediate-first
        )?;
        let connector = if let Some(dir) = &config.certs_dir {
            let config = create_tls_config_from_certs_dir(Path::new(dir), false)?;
            Some(Arc::new(config))
        } else {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set a non-empty framing suffix in the socket config (e.g. "\n" for newline-delimited messages).
  2. Match the suffix the remote endpoint actually uses to delimit messages.
  3. If the config is built in code, construct it through the builder to catch invariants earlier.
  4. Add a startup-time config check that fails fast with a clearer application-level message.

Example fix

// before
let config = SocketConfig { url, suffix: String::new(), .. };
// after
let config = SocketConfig { url, suffix: "\n".to_string(), .. };
Defensive patterns

Strategy: validation

Validate before calling

// Rust: fail fast at config load
if config.suffix.is_empty() {
    return Err(anyhow::anyhow!("socket suffix must be non-empty (e.g. \"\\n\")"));
}

Prevention

When it happens

Trigger: Connecting with a SocketConfig whose suffix field is set to "" — typically by struct-literal construction in an adapter, a config file with an empty suffix, or a placeholder left unfilled.

Common situations: Custom exchange adapters forgetting to set the message-terminating suffix (e.g. "\n" for line-framed sockets); YAML/TOML config with suffix: '' ; migrating code to a newer client that added this invariant.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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