Skip to main content

systemprompt_database/resilience/
config.rs

1//! Runtime configuration for the resilience primitives.
2//!
3//! These are the in-memory form used by [`super::guard::ResilienceGuard`].
4//! Callers that load configuration from disk (e.g. `systemprompt-models` config
5//! structs in milliseconds) translate into these `Duration`-typed structs at
6//! construction.
7
8use std::time::Duration;
9
10#[derive(Debug, Clone, Copy)]
11pub struct RetryConfig {
12    /// Counts the first try, so `1` disables retries.
13    pub max_attempts: u32,
14    /// Doubles each subsequent attempt.
15    pub base_delay: Duration,
16    pub max_delay: Duration,
17    pub jitter: bool,
18}
19
20impl Default for RetryConfig {
21    fn default() -> Self {
22        Self {
23            max_attempts: 3,
24            base_delay: Duration::from_millis(200),
25            max_delay: Duration::from_secs(10),
26            jitter: true,
27        }
28    }
29}
30
31#[derive(Debug, Clone, Copy)]
32pub struct BreakerConfig {
33    /// Consecutive (not cumulative) failures that trip the breaker open.
34    pub failure_threshold: u32,
35    pub open_cooldown: Duration,
36    pub half_open_max_probes: u32,
37}
38
39impl Default for BreakerConfig {
40    fn default() -> Self {
41        Self {
42            failure_threshold: 5,
43            open_cooldown: Duration::from_secs(30),
44            half_open_max_probes: 1,
45        }
46    }
47}
48
49#[derive(Debug, Clone, Copy)]
50pub struct BulkheadConfig {
51    pub max_concurrent: usize,
52}
53
54impl Default for BulkheadConfig {
55    fn default() -> Self {
56        Self { max_concurrent: 16 }
57    }
58}
59
60#[derive(Debug, Clone, Copy)]
61pub struct ResilienceConfig {
62    /// Per-attempt (not whole-call) timeout; non-streaming only.
63    pub request_timeout: Duration,
64    /// Max gap between two chunks before a stream is aborted.
65    pub stream_idle_timeout: Duration,
66    pub retry: RetryConfig,
67    pub breaker: BreakerConfig,
68    pub bulkhead: BulkheadConfig,
69}
70
71impl Default for ResilienceConfig {
72    fn default() -> Self {
73        Self {
74            request_timeout: Duration::from_secs(60),
75            stream_idle_timeout: Duration::from_secs(60),
76            retry: RetryConfig::default(),
77            breaker: BreakerConfig::default(),
78            bulkhead: BulkheadConfig::default(),
79        }
80    }
81}
82
83impl From<&systemprompt_models::services::ResilienceSettings> for ResilienceConfig {
84    /// Count fields are clamped to a minimum of `1`: a zero
85    /// attempt/probe/permit budget would deadlock every guarded call.
86    fn from(settings: &systemprompt_models::services::ResilienceSettings) -> Self {
87        Self {
88            request_timeout: Duration::from_millis(settings.request_timeout_ms),
89            stream_idle_timeout: Duration::from_millis(settings.stream_idle_timeout_ms),
90            retry: RetryConfig {
91                max_attempts: settings.retry_attempts.max(1),
92                base_delay: Duration::from_millis(settings.retry_base_delay_ms),
93                max_delay: Duration::from_millis(settings.retry_max_delay_ms),
94                jitter: true,
95            },
96            breaker: BreakerConfig {
97                failure_threshold: settings.breaker_failure_threshold.max(1),
98                open_cooldown: Duration::from_millis(settings.breaker_open_cooldown_ms),
99                half_open_max_probes: settings.breaker_half_open_probes.max(1),
100            },
101            bulkhead: BulkheadConfig {
102                max_concurrent: settings.max_concurrent.max(1),
103            },
104        }
105    }
106}