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
- Remove the rate_limiter argument and configure only default_quota/keyed_quotas so the limiter is built from quotas.
- Keep the shared rate_limiter and set default_quota to None and keyed_quotas to an empty vec.
- If both behaviors are needed, merge the quotas into the shared RateLimiter's own configuration before wrapping it in Arc and passing it.
- 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
- Model transport config as an enum (QuotaConfig | SharedLimiter) so an invalid combination is unrepresentable.
- Validate the merged config once at startup before constructing transports.
- Document in the config schema that rate_limiter excludes quota fields.
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
- Connection rate keys require a connection rate limiter
- Connection rate limiter requires at least one connection rat
- subscription state lock poisoned
- InstrumentState channel requires kind and currency parameter
- std::mem::take(&mut self.shutdown_errors).join("; ")
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/5dc063a8270f9004.
Report an issue: GitHub.