use std::collections::HashMap;
use std::sync::{Condvar, Mutex};
use std::time::Duration;
use crate::message::Exchange;
pub trait RoutePolicy: Send + Sync {
fn on_init(&self, _route_id: &str) {}
fn on_exchange_begin(&self, _route_id: &str, _exchange: &mut Exchange) {}
fn on_exchange_done(&self, _route_id: &str, _exchange: &Exchange) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ThrottlingScope {
Route,
Context,
}
pub struct ThrottlingInflightRoutePolicy {
pub max_inflight_exchanges: usize,
pub resume_percent_of_max: usize,
pub scope: ThrottlingScope,
state: Mutex<HashMap<String, usize>>,
cond: Condvar,
throttled: Mutex<u64>,
}
impl Default for ThrottlingInflightRoutePolicy {
fn default() -> Self {
Self::new(1000)
}
}
impl ThrottlingInflightRoutePolicy {
pub fn new(max_inflight_exchanges: usize) -> Self {
ThrottlingInflightRoutePolicy {
max_inflight_exchanges: max_inflight_exchanges.max(1),
resume_percent_of_max: 70,
scope: ThrottlingScope::Route,
state: Mutex::new(HashMap::new()),
cond: Condvar::new(),
throttled: Mutex::new(0),
}
}
pub fn with_scope(mut self, scope: ThrottlingScope) -> Self {
self.scope = scope;
self
}
pub fn with_resume_percent_of_max(mut self, p: usize) -> Self {
self.resume_percent_of_max = p.clamp(1, 100);
self
}
fn key(&self, route_id: &str) -> String {
match self.scope {
ThrottlingScope::Route => route_id.to_string(),
ThrottlingScope::Context => String::new(),
}
}
pub fn inflight(&self, route_id: &str) -> usize {
self.state.lock().unwrap().get(&self.key(route_id)).copied().unwrap_or(0)
}
pub fn throttled_count(&self) -> u64 {
*self.throttled.lock().unwrap()
}
}
impl RoutePolicy for ThrottlingInflightRoutePolicy {
fn on_exchange_begin(&self, route_id: &str, _exchange: &mut Exchange) {
let key = self.key(route_id);
let mut s = self.state.lock().unwrap();
let current = s.get(&key).copied().unwrap_or(0);
if current >= self.max_inflight_exchanges {
*self.throttled.lock().unwrap() += 1;
crate::camel_debug!("ThrottlingInflightRoutePolicy", "Throttling route {}: {} inflight exchanges", route_id, current);
let resume = (self.max_inflight_exchanges * self.resume_percent_of_max / 100).clamp(1, self.max_inflight_exchanges);
while s.get(&key).copied().unwrap_or(0) >= resume {
s = self.cond.wait_timeout(s, Duration::from_millis(50)).unwrap().0;
}
}
*s.entry(key).or_insert(0) += 1;
}
fn on_exchange_done(&self, route_id: &str, _exchange: &Exchange) {
let key = self.key(route_id);
let mut s = self.state.lock().unwrap();
if let Some(c) = s.get_mut(&key) {
*c = c.saturating_sub(1);
}
self.cond.notify_all();
}
}