use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Duration;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
mod durable_admission;
mod provider_quota;
mod sliding_window;
pub(crate) use provider_quota::observe_for_llm_call as observe_provider_quota_for_llm_call;
use provider_quota::{ObservedTokenQuota, ProviderTokenQuotaReceipt};
use sliding_window::SlidingWindow;
const DURABLE_RATE_LIMIT_ENABLED_ENV: &str = "HARN_LLM_RATE_LIMIT_DURABLE";
const DURABLE_RATE_LIMIT_STATE_PATH_ENV: &str = "HARN_LLM_RATE_LIMIT_STATE_PATH";
const DURABLE_RATE_LIMIT_MAX_WAIT_MS_ENV: &str = "HARN_LLM_RATE_LIMIT_MAX_WAIT_MS";
const DURABLE_RATE_LIMIT_MAX_WAIT_MS_DEFAULT: u64 = 30_000;
const WINDOW_SECS: u64 = 60;
const FAIR_QUEUE_STARVATION_MS: u64 = 60_000;
const RATE_LIMIT_ENV_FIELD_SUFFIXES: [&str; 5] =
["_RPM", "_TPM", "_INPUT_TPM", "_OUTPUT_TPM", "_CONCURRENCY"];
const NETWORK_BREAKER_FAILURE_THRESHOLD: u32 = 4;
const NETWORK_BREAKER_OPEN_MS: u64 = 5_000;
const NETWORK_BREAKER_OPEN_MS_SECOND: u64 = 30_000;
const NETWORK_BREAKER_OPEN_MS_MAX: u64 = 120_000;
pub(super) const UNPRODUCTIVE_COMPLETION_BREAKER_THRESHOLD: u32 = 3;
const UNPRODUCTIVE_BREAKER_OPEN_MS: u64 = 30_000;
const UNPRODUCTIVE_BREAKER_OPEN_MS_MAX: u64 = 240_000;
pub(crate) const OVERLOAD_COOLDOWN_MS: u64 = 5_000;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct RateLimitRequest {
input_tokens: u64,
output_tokens: u64,
}
impl RateLimitRequest {
fn for_llm_call(opts: &super::api::LlmCallOptions) -> Self {
let projection = super::cost::project_llm_call_cost(opts, 0.0);
Self {
input_tokens: projection.projected_input_tokens.max(0) as u64,
output_tokens: projection.projected_output_tokens.max(0) as u64,
}
}
fn total_tokens(self) -> u64 {
self.input_tokens.saturating_add(self.output_tokens)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct EffectiveRateLimits {
rpm: Option<u32>,
tpm: Option<u64>,
input_tpm: Option<u64>,
output_tpm: Option<u64>,
concurrency: Option<u32>,
}
impl EffectiveRateLimits {
fn from_catalog(mut limits: crate::llm_config::RateLimitsDef) -> Option<Self> {
if limits.is_empty() {
return None;
}
let out = Self {
rpm: limits.rpm.take(),
tpm: limits.tpm.take(),
input_tpm: limits.input_tpm.take(),
output_tpm: limits.output_tpm.take(),
concurrency: limits.concurrency.take(),
};
(!out.is_empty()).then_some(out)
}
fn is_empty(&self) -> bool {
self.rpm.is_none()
&& self.tpm.is_none()
&& self.input_tpm.is_none()
&& self.output_tpm.is_none()
&& self.concurrency.is_none()
}
fn to_catalog(&self) -> crate::llm_config::RateLimitsDef {
crate::llm_config::RateLimitsDef {
rpm: self.rpm,
tpm: self.tpm,
input_tpm: self.input_tpm,
output_tpm: self.output_tpm,
concurrency: self.concurrency,
..Default::default()
}
}
}
#[derive(Debug, Default, PartialEq, Eq)]
enum BreakerState {
#[default]
Closed,
Open { until_ms: u128 },
HalfOpen,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum BreakerOpenReason {
#[default]
Network,
UnproductiveCompletion,
}
#[derive(Debug, Default)]
struct NetworkBreaker {
state: BreakerState,
consecutive_network_failures: u32,
network_opens: u32,
consecutive_unproductive_completions: u32,
unproductive_opens: u32,
open_reason: BreakerOpenReason,
}
impl NetworkBreaker {
fn admit(&mut self, now_ms: u128) -> Option<(Duration, BreakerOpenReason)> {
let blocked = self.blocked(now_ms);
if blocked.is_none() && matches!(self.state, BreakerState::Open { .. }) {
self.state = BreakerState::HalfOpen;
}
blocked
}
fn blocked(&self, now_ms: u128) -> Option<(Duration, BreakerOpenReason)> {
match self.state {
BreakerState::Closed => None,
BreakerState::HalfOpen => {
Some((Duration::from_millis(0), self.open_reason))
}
BreakerState::Open { until_ms } => {
if now_ms >= until_ms {
None
} else {
Some((
Duration::from_millis(
until_ms.saturating_sub(now_ms).min(u128::from(u64::MAX)) as u64,
),
self.open_reason,
))
}
}
}
}
fn record_network_failure(&mut self, now_ms: u128) {
self.consecutive_network_failures = self.consecutive_network_failures.saturating_add(1);
if matches!(self.state, BreakerState::HalfOpen)
|| self.consecutive_network_failures >= NETWORK_BREAKER_FAILURE_THRESHOLD
{
self.network_opens = self.network_opens.saturating_add(1);
let window = match self.network_opens {
0 | 1 => NETWORK_BREAKER_OPEN_MS,
2 => NETWORK_BREAKER_OPEN_MS_SECOND,
_ => NETWORK_BREAKER_OPEN_MS_MAX,
};
self.open_reason = BreakerOpenReason::Network;
self.state = BreakerState::Open {
until_ms: now_ms.saturating_add(u128::from(window)),
};
}
}
fn record_unproductive_completion(&mut self, now_ms: u128) -> Option<u64> {
let was_open = matches!(self.state, BreakerState::Open { .. });
self.consecutive_unproductive_completions =
self.consecutive_unproductive_completions.saturating_add(1);
if matches!(self.state, BreakerState::HalfOpen)
|| self.consecutive_unproductive_completions
>= UNPRODUCTIVE_COMPLETION_BREAKER_THRESHOLD
{
self.unproductive_opens = self.unproductive_opens.saturating_add(1);
let shift = self.unproductive_opens.saturating_sub(1).min(3);
let window = UNPRODUCTIVE_BREAKER_OPEN_MS
.saturating_mul(1u64 << shift)
.min(UNPRODUCTIVE_BREAKER_OPEN_MS_MAX);
self.open_reason = BreakerOpenReason::UnproductiveCompletion;
self.state = BreakerState::Open {
until_ms: now_ms.saturating_add(u128::from(window)),
};
if !was_open {
return Some(window);
}
}
None
}
fn record_success(&mut self) {
self.consecutive_network_failures = 0;
self.network_opens = 0;
self.consecutive_unproductive_completions = 0;
self.unproductive_opens = 0;
self.open_reason = BreakerOpenReason::Network;
self.state = BreakerState::Closed;
}
}
struct RouteLimiter {
request_window: Option<SlidingWindow>,
total_token_window: Option<SlidingWindow>,
input_token_window: Option<SlidingWindow>,
output_token_window: Option<SlidingWindow>,
observed_token_quota: Option<ObservedTokenQuota>,
concurrency: Option<Arc<Semaphore>>,
cooldown_until_ms: Option<u128>,
breaker: NetworkBreaker,
limits: EffectiveRateLimits,
}
impl RouteLimiter {
fn new(limits: EffectiveRateLimits) -> Self {
Self {
request_window: limits.rpm.map(|rpm| SlidingWindow::new(rpm as u64)),
total_token_window: limits.tpm.map(SlidingWindow::new),
input_token_window: limits.input_tpm.map(SlidingWindow::new),
output_token_window: limits.output_tpm.map(SlidingWindow::new),
observed_token_quota: None,
concurrency: limits
.concurrency
.map(|limit| Arc::new(Semaphore::new(limit.max(1) as usize))),
cooldown_until_ms: None,
breaker: NetworkBreaker::default(),
limits,
}
}
fn check(&mut self, now_ms: u128, request: RateLimitRequest) -> Option<Duration> {
let waits = [
self.request_window
.as_mut()
.and_then(|window| window.check(now_ms, 1)),
self.total_token_window
.as_mut()
.and_then(|window| window.check(now_ms, request.total_tokens())),
self.input_token_window
.as_mut()
.and_then(|window| window.check(now_ms, request.input_tokens)),
self.output_token_window
.as_mut()
.and_then(|window| window.check(now_ms, request.output_tokens)),
self.observed_token_quota
.and_then(|quota| quota.check(now_ms, request.total_tokens())),
self.cooldown_until_ms
.filter(|until_ms| *until_ms > now_ms)
.map(|until_ms| {
Duration::from_millis(
until_ms.saturating_sub(now_ms).min(u128::from(u64::MAX)) as u64
)
}),
];
waits.into_iter().flatten().max()
}
fn record(&mut self, now_ms: u128, request: RateLimitRequest) {
if let Some(window) = self.request_window.as_mut() {
window.record(now_ms, 1);
}
if let Some(window) = self.total_token_window.as_mut() {
window.record(now_ms, request.total_tokens());
}
if let Some(window) = self.input_token_window.as_mut() {
window.record(now_ms, request.input_tokens);
}
if let Some(window) = self.output_token_window.as_mut() {
window.record(now_ms, request.output_tokens);
}
if let Some(quota) = self.observed_token_quota.as_mut() {
quota.record(now_ms, request.total_tokens());
}
}
fn record_observed_token_quota(&mut self, now_ms: u128, request: RateLimitRequest) {
if let Some(quota) = self.observed_token_quota.as_mut() {
quota.record(now_ms, request.total_tokens());
}
}
fn observe_token_quota(&mut self, now_ms: u128, receipt: ProviderTokenQuotaReceipt) {
self.observed_token_quota = Some(ObservedTokenQuota::from_receipt(now_ms, receipt));
}
fn observe_retry_after(&mut self, now_ms: u128, retry_after_ms: u64) {
if retry_after_ms == 0 {
return;
}
let until_ms = now_ms.saturating_add(u128::from(retry_after_ms));
self.cooldown_until_ms = Some(self.cooldown_until_ms.unwrap_or(0).max(until_ms));
}
fn breaker_block(&mut self, now_ms: u128) -> Option<(Duration, BreakerOpenReason)> {
self.breaker.admit(now_ms)
}
fn observe_network_failure(&mut self, now_ms: u128) {
self.breaker.record_network_failure(now_ms);
}
fn observe_unproductive_completion(&mut self, now_ms: u128) -> Option<u64> {
self.breaker.record_unproductive_completion(now_ms)
}
fn observe_success(&mut self) {
self.breaker.record_success();
}
}
#[derive(Default)]
struct RateLimitRegistry {
initialized_from_config: bool,
limiters: HashMap<String, RouteLimiter>,
}
static LIMITERS: OnceLock<Mutex<RateLimitRegistry>> = OnceLock::new();
static RUNTIME_OVERRIDES: OnceLock<Mutex<HashMap<String, EffectiveRateLimits>>> = OnceLock::new();
pub(crate) struct RateLimitPermit {
_permits: Vec<OwnedSemaphorePermit>,
}
fn registry() -> &'static Mutex<RateLimitRegistry> {
LIMITERS.get_or_init(|| Mutex::new(RateLimitRegistry::default()))
}
fn runtime_overrides() -> &'static Mutex<HashMap<String, EffectiveRateLimits>> {
RUNTIME_OVERRIDES.get_or_init(|| Mutex::new(HashMap::new()))
}
fn provider_key(provider: &str) -> String {
format!("provider:{}", provider.trim().to_ascii_lowercase())
}
fn model_key(provider: &str, model: &str) -> String {
format!(
"model:{}:{}",
provider.trim().to_ascii_lowercase(),
crate::llm_config::normalize_model_id(model.trim())
)
}
fn limiter_keys(provider: &str, model: &str) -> Vec<String> {
let provider = provider.trim();
if provider.is_empty() {
return Vec::new();
}
let mut keys = vec![provider_key(provider)];
let model = model.trim();
if !model.is_empty() {
keys.push(model_key(provider, model));
}
keys
}
fn env_key_fragment(value: &str) -> String {
let mut out = String::new();
let mut last_was_sep = false;
for ch in value.chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch.to_ascii_uppercase());
last_was_sep = false;
} else if !last_was_sep {
out.push('_');
last_was_sep = true;
}
}
out.trim_matches('_').to_string()
}
fn set_from_env_u32(slot: &mut Option<u32>, key: &str) -> bool {
let Ok(raw) = std::env::var(key) else {
return false;
};
match raw.trim().parse::<i64>() {
Ok(value) if value > 0 && value <= i64::from(u32::MAX) => *slot = Some(value as u32),
Ok(value) if value <= 0 => *slot = None,
Ok(_) => {}
Err(_) => {}
}
true
}
fn set_from_env_u64(slot: &mut Option<u64>, key: &str) -> bool {
let Ok(raw) = std::env::var(key) else {
return false;
};
match raw.trim().parse::<i64>() {
Ok(value) if value > 0 => *slot = Some(value as u64),
Ok(_) => *slot = None,
Err(_) => {}
}
true
}
fn apply_env_overrides(prefix: &str, limits: &mut EffectiveRateLimits) -> bool {
let mut changed = false;
changed |= set_from_env_u32(&mut limits.rpm, &format!("{prefix}_RPM"));
changed |= set_from_env_u64(&mut limits.tpm, &format!("{prefix}_TPM"));
changed |= set_from_env_u64(&mut limits.input_tpm, &format!("{prefix}_INPUT_TPM"));
changed |= set_from_env_u64(&mut limits.output_tpm, &format!("{prefix}_OUTPUT_TPM"));
changed |= set_from_env_u32(&mut limits.concurrency, &format!("{prefix}_CONCURRENCY"));
changed
}
fn insert_limiter(
limiters: &mut HashMap<String, RouteLimiter>,
key: String,
limits: EffectiveRateLimits,
) {
if limits.is_empty() {
limiters.remove(&key);
} else {
limiters.insert(key, RouteLimiter::new(limits));
}
}
fn limiter_for_key<'a>(
limiters: &'a mut HashMap<String, RouteLimiter>,
key: &str,
) -> &'a mut RouteLimiter {
limiters
.entry(key.to_string())
.or_insert_with(|| RouteLimiter::new(EffectiveRateLimits::default()))
}
fn provider_limits_from_config(
provider: &crate::llm_config::ProviderDef,
) -> Option<EffectiveRateLimits> {
let limits = provider
.rate_limits
.clone()
.unwrap_or_default()
.with_rpm_fallback(provider.rpm)?;
EffectiveRateLimits::from_catalog(limits)
}
fn model_limits_from_config(model: &crate::llm_config::ModelDef) -> Option<EffectiveRateLimits> {
model
.rate_limits
.clone()
.and_then(EffectiveRateLimits::from_catalog)
}
fn install_legacy_env_provider_overrides(limiters: &mut HashMap<String, RouteLimiter>) {
for (key, raw) in std::env::vars() {
let Some(fragment) = key.strip_prefix("HARN_RATE_LIMIT_") else {
continue;
};
if RATE_LIMIT_ENV_FIELD_SUFFIXES
.iter()
.any(|suffix| fragment.ends_with(suffix))
{
continue;
}
let Ok(rpm) = raw.trim().parse::<i64>() else {
continue;
};
let provider = fragment.to_ascii_lowercase();
let key = provider_key(&provider);
if rpm <= 0 {
limiters.remove(&key);
} else {
let mut limits = limiters
.get(&key)
.map(|limiter| limiter.limits.clone())
.unwrap_or_default();
limits.rpm = Some(rpm as u32);
insert_limiter(limiters, key, limits);
}
}
}
fn config_limiters_from_effective_config() -> HashMap<String, RouteLimiter> {
let config = crate::llm_config::effective_config();
let mut limiters = HashMap::new();
for (name, provider) in &config.providers {
let mut limits = provider_limits_from_config(provider).unwrap_or_default();
apply_env_overrides(
&format!("HARN_RATE_LIMIT_{}", env_key_fragment(name)),
&mut limits,
);
insert_limiter(&mut limiters, provider_key(name), limits);
}
for (model_id, model) in &config.models {
let mut limits = model_limits_from_config(model).unwrap_or_default();
apply_env_overrides(
&format!(
"HARN_RATE_LIMIT_{}_{}",
env_key_fragment(&model.provider),
env_key_fragment(model_id)
),
&mut limits,
);
insert_limiter(&mut limiters, model_key(&model.provider, model_id), limits);
}
install_legacy_env_provider_overrides(&mut limiters);
limiters
}
fn limiters_from_config_and_runtime_overrides() -> HashMap<String, RouteLimiter> {
let mut limiters = config_limiters_from_effective_config();
for (provider, limits) in runtime_overrides()
.lock()
.expect("rate limiter runtime override mutex poisoned")
.iter()
{
insert_limiter(&mut limiters, provider_key(provider), limits.clone());
}
limiters
}
#[allow(
dead_code,
reason = "explicit reload entry point is distinct from non-destructive lazy initialization"
)]
pub(crate) fn init_from_config() {
let limiters = limiters_from_config_and_runtime_overrides();
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
registry.limiters = limiters;
registry.initialized_from_config = true;
}
fn ensure_initialized_from_config() {
ensure_initialized_from_config_with(limiters_from_config_and_runtime_overrides);
}
fn ensure_initialized_from_config_with(
build_candidate: impl FnOnce() -> HashMap<String, RouteLimiter>,
) {
if registry()
.lock()
.expect("rate limiter mutex poisoned")
.initialized_from_config
{
return;
}
let limiters = build_candidate();
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
if registry.initialized_from_config {
return;
}
registry.limiters = limiters;
registry.initialized_from_config = true;
}
pub(crate) fn set_rate_limits(provider: &str, limits: crate::llm_config::RateLimitsDef) {
ensure_initialized_from_config();
let effective = EffectiveRateLimits::from_catalog(limits).unwrap_or_default();
runtime_overrides()
.lock()
.expect("rate limiter runtime override mutex poisoned")
.insert(provider.to_ascii_lowercase(), effective.clone());
insert_limiter(
&mut registry()
.lock()
.expect("rate limiter mutex poisoned")
.limiters,
provider_key(provider),
effective,
);
}
pub(crate) fn clear_rate_limit(provider: &str) {
ensure_initialized_from_config();
runtime_overrides()
.lock()
.expect("rate limiter runtime override mutex poisoned")
.remove(&provider.to_ascii_lowercase());
registry()
.lock()
.expect("rate limiter mutex poisoned")
.limiters
.remove(&provider_key(provider));
}
pub(crate) fn get_rate_limit(provider: &str) -> Option<u32> {
get_rate_limits(provider).and_then(|limits| limits.rpm)
}
pub(crate) fn get_rate_limits(provider: &str) -> Option<crate::llm_config::RateLimitsDef> {
ensure_initialized_from_config();
registry()
.lock()
.expect("rate limiter mutex poisoned")
.limiters
.get(&provider_key(provider))
.map(|limiter| limiter.limits.to_catalog())
}
fn max_wait(left: Option<Duration>, right: Option<Duration>) -> Option<Duration> {
match (left, right) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}
async fn acquire_concurrency(keys: &[String]) -> Vec<OwnedSemaphorePermit> {
let semaphores = {
let registry = registry().lock().expect("rate limiter mutex poisoned");
keys.iter()
.filter_map(|key| {
registry
.limiters
.get(key)
.and_then(|limiter| limiter.concurrency.clone())
})
.collect::<Vec<_>>()
};
let mut permits = Vec::with_capacity(semaphores.len());
for semaphore in semaphores {
if let Ok(permit) = semaphore.acquire_owned().await {
permits.push(permit);
}
}
permits
}
fn check_wait_for_keys(
registry: &mut RateLimitRegistry,
keys: &[String],
request: RateLimitRequest,
now_ms: u128,
) -> Option<Duration> {
let mut wait = None;
for key in keys {
if let Some(limiter) = registry.limiters.get_mut(key) {
wait = max_wait(wait, limiter.check(now_ms, request));
}
}
wait
}
fn record_for_keys(
registry: &mut RateLimitRegistry,
keys: &[String],
request: RateLimitRequest,
now_ms: u128,
) {
for key in keys {
if let Some(limiter) = registry.limiters.get_mut(key) {
limiter.record(now_ms, request);
}
}
}
fn record_observed_quota_for_keys(
registry: &mut RateLimitRegistry,
keys: &[String],
request: RateLimitRequest,
now_ms: u128,
) {
for key in keys {
if let Some(limiter) = registry.limiters.get_mut(key) {
limiter.record_observed_token_quota(now_ms, request);
}
}
}
fn durable_rate_limit_disabled() -> bool {
let Ok(raw) = std::env::var(DURABLE_RATE_LIMIT_ENABLED_ENV) else {
return false;
};
matches!(
raw.trim().to_ascii_lowercase().as_str(),
"0" | "false" | "off" | "none" | "disabled"
)
}
fn durable_max_wait_ms() -> Option<u64> {
let configured = match std::env::var(DURABLE_RATE_LIMIT_MAX_WAIT_MS_ENV) {
Ok(raw) => match raw.trim().parse::<u64>() {
Ok(value) => value,
Err(_) => DURABLE_RATE_LIMIT_MAX_WAIT_MS_DEFAULT,
},
Err(_) => DURABLE_RATE_LIMIT_MAX_WAIT_MS_DEFAULT,
};
if configured == 0 {
None
} else {
Some(configured)
}
}
fn durable_state_path() -> Option<PathBuf> {
if durable_rate_limit_disabled() {
return None;
}
if let Ok(raw) = std::env::var(DURABLE_RATE_LIMIT_STATE_PATH_ENV) {
let trimmed = raw.trim();
if !trimmed.is_empty() {
let path = PathBuf::from(trimmed);
return if path.is_absolute() {
Some(path)
} else {
std::env::current_dir().ok().map(|cwd| cwd.join(path))
};
}
}
let base = crate::stdlib::process::runtime_root_base();
Some(crate::runtime_paths::state_root(&base).join("llm-rate-limits.sqlite"))
}
fn durable_bucket(
key: &str,
suffix: &str,
limit: u64,
units: u64,
) -> crate::durable_rate_limit::RateBucket {
crate::durable_rate_limit::RateBucket::new(
format!("llm:{key}:{suffix}"),
limit.max(1),
units,
WINDOW_SECS * 1000,
)
}
fn durable_buckets_for_keys(
registry: &RateLimitRegistry,
keys: &[String],
request: RateLimitRequest,
) -> Vec<crate::durable_rate_limit::RateBucket> {
let mut buckets = Vec::new();
for key in keys {
let Some(limiter) = registry.limiters.get(key) else {
continue;
};
if let Some(rpm) = limiter.limits.rpm {
buckets.push(durable_bucket(key, "rpm", u64::from(rpm), 1));
}
if let Some(tpm) = limiter.limits.tpm {
buckets.push(durable_bucket(key, "tpm", tpm, request.total_tokens()));
}
if let Some(input_tpm) = limiter.limits.input_tpm {
buckets.push(durable_bucket(
key,
"input_tpm",
input_tpm,
request.input_tokens,
));
}
if let Some(output_tpm) = limiter.limits.output_tpm {
buckets.push(durable_bucket(
key,
"output_tpm",
output_tpm,
request.output_tokens,
));
}
}
buckets
}
async fn sleep_after_throttle(provider: &str, model: &str, duration: Duration) {
let route = if model.trim().is_empty() {
provider.to_string()
} else {
format!(
"{provider}/{}",
crate::llm_config::normalize_model_id(model)
)
};
crate::events::log_info(
"llm.rate_limit",
&format!(
"Rate limit for '{}': throttling for {}ms",
route,
duration.as_millis()
),
);
crate::clock_mock::sleep(duration).await;
}
async fn acquire_permit_for(
provider: &str,
model: &str,
request: RateLimitRequest,
consumer_id: Option<&str>,
session_id: Option<&str>,
reroute_on_timeout: bool,
) -> Result<RateLimitPermit, crate::value::VmError> {
ensure_initialized_from_config();
let keys = limiter_keys(provider, model);
if let Some(state_path) = durable_state_path() {
loop {
let permits = acquire_concurrency(&keys).await;
if let Some(duration) = {
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
let now_ms = crate::clock_mock::instant_now().as_millis();
check_wait_for_keys(&mut registry, &keys, request, now_ms)
} {
drop(permits);
sleep_after_throttle(provider, model, duration).await;
continue;
}
durable_admission::acquire(
state_path,
provider,
model,
&keys,
request,
consumer_id,
session_id,
reroute_on_timeout,
)
.await?;
return Ok(provider_quota::admit_after_durable(
provider, model, &keys, request, permits,
)
.await);
}
}
loop {
if let Some(duration) = {
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
let now_ms = crate::clock_mock::instant_now().as_millis();
check_wait_for_keys(&mut registry, &keys, request, now_ms)
} {
sleep_after_throttle(provider, model, duration).await;
continue;
}
let permits = acquire_concurrency(&keys).await;
if let Some(duration) = {
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
let now_ms = crate::clock_mock::instant_now().as_millis();
let wait = check_wait_for_keys(&mut registry, &keys, request, now_ms);
if wait.is_none() {
record_for_keys(&mut registry, &keys, request, now_ms);
}
wait
} {
drop(permits);
sleep_after_throttle(provider, model, duration).await;
continue;
}
return Ok(RateLimitPermit { _permits: permits });
}
}
pub(crate) fn observe_retry_after_for_llm_call(
opts: &super::api::LlmCallOptions,
retry_after_ms: u64,
) {
if retry_after_ms == 0 {
return;
}
ensure_initialized_from_config();
let keys = limiter_keys(&opts.provider, &opts.model);
let now_ms = crate::clock_mock::instant_now().as_millis();
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
for key in keys {
limiter_for_key(&mut registry.limiters, &key).observe_retry_after(now_ms, retry_after_ms);
}
}
fn breaker_open_error(
provider: &str,
model: &str,
remaining: Duration,
reason: BreakerOpenReason,
) -> crate::value::VmError {
let route = if model.trim().is_empty() {
provider.to_string()
} else {
format!(
"{provider}/{}",
crate::llm_config::normalize_model_id(model)
)
};
match reason {
BreakerOpenReason::Network => crate::value::VmError::CategorizedError {
message: format!(
"network circuit breaker open for '{route}': sustained network failures or \
provider overload; failing fast for {}ms (a half-open probe will follow)",
remaining.as_millis()
),
category: crate::value::ErrorCategory::TransientNetwork,
},
BreakerOpenReason::UnproductiveCompletion => {
let message = format!(
"circuit breaker open for '{route}': sustained empty/unproductive completions \
(served but delivered no content, reasoning, or tool call); failing fast for \
{}ms (a half-open probe will follow)",
remaining.as_millis()
);
crate::value::VmError::Thrown(crate::value::VmValue::dict(
std::collections::BTreeMap::from([
(
"category".to_string(),
crate::value::VmValue::String(arcstr::ArcStr::from("circuit_open")),
),
(
"code".to_string(),
crate::value::VmValue::String(arcstr::ArcStr::from("route_quarantined")),
),
(
"reason".to_string(),
crate::value::VmValue::String(arcstr::ArcStr::from(
"unproductive_completion",
)),
),
("attempt_count".to_string(), crate::value::VmValue::Int(0)),
(
"remaining_ms".to_string(),
crate::value::VmValue::Int(
remaining.as_millis().try_into().unwrap_or(i64::MAX),
),
),
(
"message".to_string(),
crate::value::VmValue::String(arcstr::ArcStr::from(message)),
),
]),
))
}
}
}
mod network_recovery;
pub(crate) use network_recovery::await_network_breaker_for_llm_call;
#[cfg(test)]
pub(crate) use network_recovery::check_network_breaker_for_llm_call;
#[cfg(test)]
use network_recovery::network_breaker_admission;
pub(crate) fn observe_unproductive_completion_for_llm_call(
opts: &super::api::LlmCallOptions,
reason: &str,
) {
ensure_initialized_from_config();
let key = if opts.model.trim().is_empty() {
provider_key(&opts.provider)
} else {
model_key(&opts.provider, &opts.model)
};
let now_ms = crate::clock_mock::instant_now().as_millis();
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
let cooldown_ms =
limiter_for_key(&mut registry.limiters, &key).observe_unproductive_completion(now_ms);
drop(registry);
if let Some(cooldown_ms) = cooldown_ms {
super::append_observability_sidecar_entry(
"route_quarantined",
serde_json::Map::from_iter([
(
"schema".to_string(),
serde_json::json!("harn.llm.route_quarantine.v1"),
),
(
"receipt_kind".to_string(),
serde_json::json!("route_quarantined"),
),
("provider".to_string(), serde_json::json!(opts.provider)),
("model".to_string(), serde_json::json!(opts.model)),
("reason".to_string(), serde_json::json!(reason)),
("cooldown_ms".to_string(), serde_json::json!(cooldown_ms)),
]),
);
}
}
pub(crate) fn observe_network_outcome_for_llm_call(
opts: &super::api::LlmCallOptions,
network_failure: bool,
) {
ensure_initialized_from_config();
let keys = limiter_keys(&opts.provider, &opts.model);
let now_ms = crate::clock_mock::instant_now().as_millis();
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
for key in keys {
let limiter = limiter_for_key(&mut registry.limiters, &key);
if network_failure {
limiter.observe_network_failure(now_ms);
} else {
limiter.observe_success();
}
}
}
pub(crate) async fn acquire_permit(
provider: &str,
) -> Result<RateLimitPermit, crate::value::VmError> {
acquire_permit_for(provider, "", RateLimitRequest::default(), None, None, false).await
}
pub(crate) async fn acquire_permit_for_llm_call(
opts: &super::api::LlmCallOptions,
) -> Result<RateLimitPermit, crate::value::VmError> {
acquire_permit_for(
&opts.provider,
&opts.model,
RateLimitRequest::for_llm_call(opts),
opts.rate_limit_consumer_id
.as_deref()
.or(opts.session_id.as_deref()),
opts.session_id.as_deref(),
opts.rate_limit_reroute_on_timeout,
)
.await
}
pub(crate) fn reset_rate_limit_state() {
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
registry.limiters.clear();
registry.initialized_from_config = false;
drop(registry);
runtime_overrides()
.lock()
.expect("rate limiter runtime override mutex poisoned")
.clear();
}
pub(crate) fn reset_runtime_rate_limit_overrides() {
let cleared_provider_keys = {
let mut overrides = runtime_overrides()
.lock()
.expect("rate limiter runtime override mutex poisoned");
if overrides.is_empty() {
return;
}
let keys = overrides
.keys()
.map(|provider| provider_key(provider))
.collect::<Vec<_>>();
overrides.clear();
keys
};
let mut config_limiters = config_limiters_from_effective_config();
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
for key in cleared_provider_keys {
if let Some(limiter) = config_limiters.remove(&key) {
registry.limiters.insert(key, limiter);
} else {
registry.limiters.remove(&key);
}
}
}
#[cfg(test)]
fn get_model_rate_limits(provider: &str, model: &str) -> Option<crate::llm_config::RateLimitsDef> {
ensure_initialized_from_config();
registry()
.lock()
.expect("rate limiter mutex poisoned")
.limiters
.get(&model_key(provider, model))
.map(|limiter| limiter.limits.to_catalog())
}
#[cfg(test)]
fn provider_request_usage(provider: &str) -> u64 {
ensure_initialized_from_config();
let mut registry = registry().lock().expect("rate limiter mutex poisoned");
let Some(limiter) = registry.limiters.get_mut(&provider_key(provider)) else {
return 0;
};
let Some(window) = limiter.request_window.as_mut() else {
return 0;
};
window.prune(crate::clock_mock::instant_now().as_millis());
window.usage()
}
#[cfg(test)]
mod tests;