nautechsystems/nautilus_trader · error · TransportError

Cannot combine a shared rate limiter with quota configuratio

Error message

Cannot combine a shared rate limiter with quota configuration

What it means

The WebSocket transport factory builds a rate limiter either from quota configuration (a default quota and/or per-key quotas) or accepts a caller-supplied shared Arc<RateLimiter>. These two configuration modes are mutually exclusive: if a pre-built rate limiter is provided while default_quota or keyed_quotas are also set, it cannot decide which policy wins, so it returns an Io InvalidInput error wrapped in TransportError.

Source

Thrown at crates/network/src/websocket/client.rs:2872

        if rate_limiter.is_some() && keys.is_empty() {
            return Err(TransportError::Io(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "Connection rate limiter requires at least one connection rate key",
            )));
        }

        Ok(rate_limiter.map(|limiter| ConnectionRateLimit { limiter, keys }))
    }

    fn resolve_rate_limiter(
        default_quota: Option<Quota>,
        keyed_quotas: Vec<(String, Quota)>,
        rate_limiter: Option<Arc<RateLimiter<Ustr, MonotonicClock>>>,
    ) -> Result<Arc<RateLimiter<Ustr, MonotonicClock>>, TransportError> {
        if let Some(rate_limiter) = rate_limiter {
            if default_quota.is_some() || !keyed_quotas.is_empty() {
                return Err(TransportError::Io(std::io::Error::new(
                    std::io::ErrorKind::InvalidInput,
                    "Cannot combine a shared rate limiter with quota configuration",
                )));
            }
            return Ok(rate_limiter);
        }

        let keyed_quotas = keyed_quotas
            .into_iter()
            .map(|(key, quota)| (Ustr::from(&key), quota))
            .collect();
        Ok(Arc::new(RateLimiter::new_with_quota(
            default_quota,
            keyed_quotas,
        )))
    }

    async fn connect_with_handler_scoped(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Remove the rate_limiter argument and configure only default_quota/keyed_quotas so the limiter is built from quotas.
  2. Keep the shared rate_limiter and set default_quota to None and keyed_quotas to an empty vec.
  3. If both behaviors are needed, merge the quotas into the shared RateLimiter's own configuration before wrapping it in Arc and passing it.
  4. Pre-validate the config at startup and surface a clear user-facing message instead of relying on this transport error.

Example fix

// before
let limiter = Some(Arc::new(RateLimiter::new(clock)));
let transport = build_transport(limiter, Some(default_quota), keyed_quotas); // errors
// after
let transport = build_transport(None, Some(default_quota), keyed_quotas); // quotas build the limiter
// or
let transport = build_transport(limiter, None, Vec::new()); // shared limiter only
Defensive patterns

Strategy: validation

Validate before calling

if rate_limiter.is_some() && (default_quota.is_some() || !keyed_quotas.is_empty()) {
    return Err("Provide either a shared rate_limiter OR quota configuration, not both".into());
}

Prevention

When it happens

Trigger: Calling the transport/factory constructor passing rate_limiter: Some(Arc<RateLimiter<...>>) together with a non-None default_quota or a non-empty keyed_quotas vec.

Common situations: Config files or environment-driven setups where a user pastes both a 'rate limiter' preset and quota settings; combining an adapter's built-in shared limiter with custom per-endpoint quotas; copying example code that already sets quotas while a shared limiter remains enabled from an earlier call.

Related errors


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