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