use std::time::Duration;
use backon::ExponentialBuilder;
use serde::{Deserialize, Serialize};
use crate::governor::RateLimitConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SinkStackConfig {
#[serde(default)]
pub max_concurrency: usize,
#[serde(default)]
pub adaptive: Option<AdaptiveConfig>,
#[serde(default = "default_attempt_timeout_ms")]
pub attempt_timeout_ms: u64,
#[serde(default)]
pub load_shed: bool,
#[serde(default = "default_max_retries")]
pub max_retries: usize,
#[serde(default = "default_min_backoff_ms")]
pub min_backoff_ms: u64,
#[serde(default = "default_max_backoff_ms")]
pub max_backoff_ms: u64,
#[serde(default)]
pub rate_limit: RateLimitConfig,
}
fn default_attempt_timeout_ms() -> u64 {
30_000
}
fn default_max_retries() -> usize {
3
}
fn default_min_backoff_ms() -> u64 {
100
}
fn default_max_backoff_ms() -> u64 {
30_000
}
impl Default for SinkStackConfig {
fn default() -> Self {
Self {
max_concurrency: 0,
adaptive: None,
attempt_timeout_ms: default_attempt_timeout_ms(),
load_shed: false,
max_retries: default_max_retries(),
min_backoff_ms: default_min_backoff_ms(),
max_backoff_ms: default_max_backoff_ms(),
rate_limit: RateLimitConfig::default(),
}
}
}
impl SinkStackConfig {
#[must_use]
pub fn from_cascade() -> Self {
Self::from_cascade_key("sink_stack")
}
#[must_use]
pub fn from_cascade_key(key: &str) -> Self {
#[cfg(feature = "config")]
{
if let Some(cfg) = crate::config::try_get()
&& let Ok(s) = cfg.unmarshal_key_registered::<Self>(key)
{
return s;
}
}
#[cfg(not(feature = "config"))]
let _ = key;
Self::default()
}
#[must_use]
pub fn attempt_timeout(&self) -> Duration {
if self.attempt_timeout_ms == 0 {
Duration::from_secs(86_400)
} else {
Duration::from_millis(self.attempt_timeout_ms)
}
}
#[must_use]
pub fn backoff(&self) -> ExponentialBuilder {
ExponentialBuilder::new()
.with_min_delay(Duration::from_millis(self.min_backoff_ms))
.with_max_delay(Duration::from_millis(self.max_backoff_ms))
.with_jitter()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct AdaptiveConfig {
#[serde(default = "default_initial_limit")]
pub initial_limit: usize,
#[serde(default = "default_min_limit")]
pub min_limit: usize,
#[serde(default = "default_max_limit")]
pub max_limit: usize,
#[serde(default = "default_increase_by")]
pub increase_by: usize,
#[serde(default = "default_decrease_factor")]
pub decrease_factor: f64,
}
fn default_initial_limit() -> usize {
10
}
fn default_min_limit() -> usize {
1
}
fn default_max_limit() -> usize {
100
}
fn default_increase_by() -> usize {
1
}
fn default_decrease_factor() -> f64 {
0.5
}
impl Default for AdaptiveConfig {
fn default() -> Self {
Self {
initial_limit: default_initial_limit(),
min_limit: default_min_limit(),
max_limit: default_max_limit(),
increase_by: default_increase_by(),
decrease_factor: default_decrease_factor(),
}
}
}
impl AdaptiveConfig {
#[must_use]
pub fn build_limiter(&self) -> std::sync::Arc<super::adaptive::AdaptiveLimiter> {
super::adaptive::AdaptiveLimiter::new(
self.initial_limit,
self.min_limit,
self.max_limit,
self.increase_by,
self.decrease_factor,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_preserve_behaviour() {
let c = SinkStackConfig::default();
assert_eq!(c.max_concurrency, 0, "no concurrency cap by default");
assert!(c.adaptive.is_none(), "ARC off by default (static gate)");
assert!(!c.load_shed, "queue, do not shed, by default");
assert_eq!(c.max_retries, 3);
assert!(!c.rate_limit.is_enabled(), "no rate limit by default");
assert_eq!(c.attempt_timeout(), Duration::from_secs(30));
}
#[test]
fn zero_timeout_means_effectively_disabled() {
let c = SinkStackConfig {
attempt_timeout_ms: 0,
..Default::default()
};
assert!(c.attempt_timeout() >= Duration::from_secs(3600));
}
#[test]
fn deserialise_from_yaml() {
let yaml = r"
max_concurrency: 8
attempt_timeout_ms: 5000
load_shed: true
max_retries: 5
rate_limit:
rps: 100
";
let c: SinkStackConfig = serde_yaml_ng::from_str(yaml).unwrap();
assert_eq!(c.max_concurrency, 8);
assert_eq!(c.attempt_timeout_ms, 5000);
assert!(c.load_shed);
assert_eq!(c.max_retries, 5);
assert_eq!(c.rate_limit.rps, 100);
}
}