nautechsystems/nautilus_trader · error

heartbeat_secs must be positive when set

Error message

heartbeat_secs must be positive when set

What it means

The Betfair streaming config validator rejects a heartbeat interval of exactly 0 seconds. A zero heartbeat is meaningless (it would mean 'ping continuously'), so configuration with Some(0) fails validation before the stream connects. This guard runs inside StreamConfig::validate, called by connect_inner.

Source

Thrown at crates/adapters/betfair/src/stream/config.rs:79

    }
}

impl BetfairStreamConfig {
    #[must_use]
    pub fn dead_peer_timeout_secs(&self) -> u64 {
        self.heartbeat_timeout_secs
            .unwrap_or(DEAD_PEER_TIMEOUT_MIN_SECS)
    }

    /// Validates heartbeat settings.
    ///
    /// # Errors
    ///
    /// Returns an error if an outbound interval is zero or an explicit dead-peer timeout is
    /// shorter than two server intervals.
    pub fn validate(&self) -> anyhow::Result<()> {
        if self.heartbeat_secs == Some(0) {
            anyhow::bail!("heartbeat_secs must be positive when set");
        }

        if let Some(timeout_secs) = self.heartbeat_timeout_secs
            && timeout_secs < DEAD_PEER_TIMEOUT_MIN_SECS
        {
            anyhow::bail!(
                "heartbeat_timeout_secs must cover at least two server heartbeat intervals \
                 ({DEAD_PEER_TIMEOUT_MIN_SECS}s), was {timeout_secs}s",
            );
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use rstest::rstest;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set heartbeat_secs to a positive value, e.g. Some(10) matching Betfair's recommended heartbeat (typically 5-30 seconds).
  2. If you want no explicit heartbeat, leave the field as None instead of Some(0).
  3. Fix the source of the value (env var, config file, CLI default) so it never produces 0.

Example fix

// before
let mut config = StreamConfig::default();
config.heartbeat_secs = Some(0); // fails validation
// after
config.heartbeat_secs = Some(10); // or leave as None
Defensive patterns

Strategy: validation

Validate before calling

if let Some(hb) = heartbeat_secs {
    assert!(hb > 0, "heartbeat_secs must be positive when set");
}

Try / catch

let config = StreamConfig { heartbeat_secs, .. };
if let Err(e) = config.validate() {
    return Err(format!("invalid stream config: {e}"));
}

Prevention

When it happens

Trigger: Constructing a Betfair stream config with heartbeat_secs = Some(0) and then calling connect (via connect_inner -> validate).

Common situations: Setting the value from an environment variable parsed to 0 by default; a user typing heartbeat=0 intending 'disable heartbeat' instead of leaving it None; templated config files filled with placeholder zeros.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — 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/5d33549d2aab1608. Report an issue: GitHub.