1use confique::Config;
8use schemars::{JsonSchema, Schema, SchemaGenerator};
9use serde::{Deserialize, Serialize};
10use std::borrow::Cow;
11use std::time::Duration;
12
13use crate::id::types::ChildId;
14use crate::policy::budget as runtime_budget;
15use crate::policy::failure_window as runtime_failure_window;
16use crate::policy::group::{GroupDependencyEdge, PropagationPolicy};
17use crate::policy::meltdown::MeltdownPolicy;
18use crate::policy::task_role_defaults::{SeverityClass, TaskRole};
19use crate::spec::supervisor::{
20 ChildStrategyOverride, DynamicSupervisorPolicy, EscalationPolicy,
21 GroupConfig as RuntimeGroupConfig, GroupStrategy, RestartLimit, SupervisionStrategy,
22};
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
26pub struct RestartBudgetConfig {
27 #[config(default = 60)]
29 #[serde(default = "default_restart_budget_window_secs")]
30 pub window_secs: u64,
31 #[config(default = 10)]
33 #[serde(default = "default_restart_budget_max_burst")]
34 pub max_burst: u32,
35 #[config(default = 0.5)]
37 #[serde(default = "default_restart_budget_recovery_rate")]
38 pub recovery_rate_per_sec: f64,
39}
40
41impl RestartBudgetConfig {
42 pub fn to_runtime(&self) -> runtime_budget::RestartBudgetConfig {
52 runtime_budget::RestartBudgetConfig::new(
53 Duration::from_secs(self.window_secs),
54 self.max_burst,
55 self.recovery_rate_per_sec,
56 )
57 }
58}
59
60impl Default for RestartBudgetConfig {
61 fn default() -> Self {
63 Self {
64 window_secs: default_restart_budget_window_secs(),
65 max_burst: default_restart_budget_max_burst(),
66 recovery_rate_per_sec: default_restart_budget_recovery_rate(),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73#[serde(rename_all = "snake_case")]
74pub enum FailureWindowMode {
75 TimeSliding,
77 CountSliding,
79}
80
81impl Default for FailureWindowMode {
82 fn default() -> Self {
84 Self::TimeSliding
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
90pub struct FailureWindowConfig {
91 #[config(default = "time_sliding")]
93 #[serde(default)]
94 pub mode: FailureWindowMode,
95 #[config(default = 60)]
97 #[serde(default = "default_failure_window_secs")]
98 pub window_secs: u64,
99 #[config(default = 5)]
101 #[serde(default = "default_failure_window_max_count")]
102 pub max_count: usize,
103 #[config(default = 5)]
105 #[serde(default = "default_failure_window_threshold")]
106 pub threshold: usize,
107}
108
109impl FailureWindowConfig {
110 pub fn to_runtime(&self) -> runtime_failure_window::FailureWindowConfig {
120 match self.mode {
121 FailureWindowMode::TimeSliding => {
122 runtime_failure_window::FailureWindowConfig::time_sliding(
123 self.window_secs,
124 self.threshold,
125 )
126 }
127 FailureWindowMode::CountSliding => {
128 runtime_failure_window::FailureWindowConfig::count_sliding(
129 self.max_count,
130 self.threshold,
131 )
132 }
133 }
134 }
135}
136
137impl Default for FailureWindowConfig {
138 fn default() -> Self {
140 Self {
141 mode: FailureWindowMode::default(),
142 window_secs: default_failure_window_secs(),
143 max_count: default_failure_window_max_count(),
144 threshold: default_failure_window_threshold(),
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
151pub struct MeltdownConfig {
152 #[config(default = 3)]
154 #[serde(default = "default_meltdown_child_max_restarts")]
155 pub child_max_restarts: u32,
156 #[config(default = 10)]
158 #[serde(default = "default_meltdown_child_window_secs")]
159 pub child_window_secs: u64,
160 #[config(default = 5)]
162 #[serde(default = "default_meltdown_group_max_failures")]
163 pub group_max_failures: u32,
164 #[config(default = 30)]
166 #[serde(default = "default_meltdown_group_window_secs")]
167 pub group_window_secs: u64,
168 #[config(default = 10)]
170 #[serde(default = "default_meltdown_supervisor_max_failures")]
171 pub supervisor_max_failures: u32,
172 #[config(default = 60)]
174 #[serde(default = "default_meltdown_supervisor_window_secs")]
175 pub supervisor_window_secs: u64,
176 #[config(default = 120)]
178 #[serde(default = "default_meltdown_reset_after_secs")]
179 pub reset_after_secs: u64,
180}
181
182impl MeltdownConfig {
183 pub fn to_runtime(&self) -> MeltdownPolicy {
193 MeltdownPolicy::new(
194 self.child_max_restarts,
195 Duration::from_secs(self.child_window_secs),
196 self.group_max_failures,
197 Duration::from_secs(self.group_window_secs),
198 self.supervisor_max_failures,
199 Duration::from_secs(self.supervisor_window_secs),
200 Duration::from_secs(self.reset_after_secs),
201 )
202 }
203}
204
205impl Default for MeltdownConfig {
206 fn default() -> Self {
208 Self {
209 child_max_restarts: default_meltdown_child_max_restarts(),
210 child_window_secs: default_meltdown_child_window_secs(),
211 group_max_failures: default_meltdown_group_max_failures(),
212 group_window_secs: default_meltdown_group_window_secs(),
213 supervisor_max_failures: default_meltdown_supervisor_max_failures(),
214 supervisor_window_secs: default_meltdown_supervisor_window_secs(),
215 reset_after_secs: default_meltdown_reset_after_secs(),
216 }
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
222pub struct SupervisionPipelineConfig {
223 #[config(default = 100)]
225 #[serde(default = "default_pipeline_journal_capacity")]
226 pub journal_capacity: usize,
227 #[config(default = 10)]
229 #[serde(default = "default_pipeline_subscriber_capacity")]
230 pub subscriber_capacity: usize,
231 #[config(default = 5)]
233 #[serde(default = "default_concurrent_restart_limit")]
234 pub concurrent_restart_limit: u32,
235}
236
237impl Default for SupervisionPipelineConfig {
238 fn default() -> Self {
240 Self {
241 journal_capacity: default_pipeline_journal_capacity(),
242 subscriber_capacity: default_pipeline_subscriber_capacity(),
243 concurrent_restart_limit: default_concurrent_restart_limit(),
244 }
245 }
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
250pub struct DynamicSupervisorConfig {
251 #[config(default = true)]
253 #[serde(default = "default_true")]
254 pub enabled: bool,
255 #[schemars(!default)]
257 #[serde(default)]
258 pub child_limit: Option<usize>,
259}
260
261impl DynamicSupervisorConfig {
262 pub fn to_runtime(&self) -> DynamicSupervisorPolicy {
272 DynamicSupervisorPolicy {
273 enabled: self.enabled,
274 child_limit: self.child_limit,
275 }
276 }
277}
278
279impl Default for DynamicSupervisorConfig {
280 fn default() -> Self {
282 Self {
283 enabled: true,
284 child_limit: None,
285 }
286 }
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
291pub struct RestartLimitConfig {
292 pub max_restarts: u32,
294 pub window_ms: u64,
296}
297
298impl RestartLimitConfig {
299 pub fn to_runtime(&self) -> RestartLimit {
309 RestartLimit::new(self.max_restarts, Duration::from_millis(self.window_ms))
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Config)]
318pub struct GroupsConfigSection {
319 #[config(default = [])]
321 pub items: Vec<GroupConfig>,
322}
323
324impl Default for GroupsConfigSection {
325 fn default() -> Self {
327 Self { items: Vec::new() }
328 }
329}
330
331impl JsonSchema for GroupsConfigSection {
332 fn schema_name() -> Cow<'static, str> {
334 Cow::Borrowed("GroupsConfigSection")
335 }
336
337 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
339 Vec::<GroupConfig>::json_schema(generator)
340 }
341}
342
343impl Serialize for GroupsConfigSection {
344 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
346 where
347 S: confique::serde::Serializer,
348 {
349 self.items.serialize(serializer)
350 }
351}
352
353impl<'de> Deserialize<'de> for GroupsConfigSection {
354 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
356 where
357 D: confique::serde::Deserializer<'de>,
358 {
359 Ok(Self {
360 items: Vec::<GroupConfig>::deserialize(deserializer)?,
361 })
362 }
363}
364
365impl GroupsConfigSection {
366 pub fn as_slice(&self) -> &[GroupConfig] {
368 &self.items
369 }
370
371 pub fn len(&self) -> usize {
373 self.items.len()
374 }
375
376 pub fn is_empty(&self) -> bool {
378 self.items.is_empty()
379 }
380}
381
382impl From<GroupsConfigSection> for Vec<GroupConfig> {
383 fn from(section: GroupsConfigSection) -> Self {
385 section.items
386 }
387}
388
389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
391pub struct GroupConfig {
392 pub name: String,
394 #[schemars(!default)]
396 #[serde(default)]
397 pub budget: Option<RestartBudgetConfig>,
398}
399
400impl GroupConfig {
401 pub fn to_runtime(&self, members: &[ChildId]) -> RuntimeGroupConfig {
411 RuntimeGroupConfig::new(
412 self.name.clone(),
413 members.to_vec(),
414 self.budget.as_ref().map(RestartBudgetConfig::to_runtime),
415 )
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
421pub struct GroupStrategyConfig {
422 pub group: String,
424 pub strategy: SupervisionStrategy,
426 #[schemars(!default)]
428 #[serde(default)]
429 pub restart_limit: Option<RestartLimitConfig>,
430 #[schemars(!default)]
432 #[serde(default)]
433 pub escalation_policy: Option<EscalationPolicy>,
434}
435
436impl GroupStrategyConfig {
437 pub fn to_runtime(&self) -> GroupStrategy {
447 let mut strategy = GroupStrategy::new(self.group.clone(), self.strategy);
448 strategy.restart_limit = self
449 .restart_limit
450 .as_ref()
451 .map(RestartLimitConfig::to_runtime);
452 strategy.escalation_policy = self.escalation_policy;
453 strategy
454 }
455}
456
457#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
459pub struct ChildStrategyOverrideConfig {
460 pub child_id: String,
462 pub strategy: SupervisionStrategy,
464 #[schemars(!default)]
466 #[serde(default)]
467 pub restart_limit: Option<RestartLimitConfig>,
468 #[schemars(!default)]
470 #[serde(default)]
471 pub escalation_policy: Option<EscalationPolicy>,
472}
473
474impl ChildStrategyOverrideConfig {
475 pub fn to_runtime(&self) -> ChildStrategyOverride {
485 let mut override_config =
486 ChildStrategyOverride::new(ChildId::new(&self.child_id), self.strategy);
487 override_config.restart_limit = self
488 .restart_limit
489 .as_ref()
490 .map(RestartLimitConfig::to_runtime);
491 override_config.escalation_policy = self.escalation_policy;
492 override_config
493 }
494}
495
496#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
498pub struct GroupDependencyConfig {
499 pub from_group: String,
501 pub to_group: String,
503 pub propagation: PropagationPolicy,
505}
506
507impl GroupDependencyConfig {
508 pub fn to_runtime(&self) -> GroupDependencyEdge {
518 GroupDependencyEdge {
519 from_group: self.from_group.clone(),
520 to_group: self.to_group.clone(),
521 propagation: self.propagation,
522 }
523 }
524}
525
526#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
528pub struct SeverityDefaultConfig {
529 pub task_role: TaskRole,
531 pub severity: SeverityClass,
533}
534
535fn default_restart_budget_window_secs() -> u64 {
537 60
538}
539
540fn default_restart_budget_max_burst() -> u32 {
542 10
543}
544
545fn default_restart_budget_recovery_rate() -> f64 {
547 0.5
548}
549
550fn default_failure_window_secs() -> u64 {
552 60
553}
554
555fn default_failure_window_max_count() -> usize {
557 5
558}
559
560fn default_failure_window_threshold() -> usize {
562 5
563}
564
565fn default_meltdown_child_max_restarts() -> u32 {
567 3
568}
569
570fn default_meltdown_child_window_secs() -> u64 {
572 10
573}
574
575fn default_meltdown_group_max_failures() -> u32 {
577 5
578}
579
580fn default_meltdown_group_window_secs() -> u64 {
582 30
583}
584
585fn default_meltdown_supervisor_max_failures() -> u32 {
587 10
588}
589
590fn default_meltdown_supervisor_window_secs() -> u64 {
592 60
593}
594
595fn default_meltdown_reset_after_secs() -> u64 {
597 120
598}
599
600fn default_pipeline_journal_capacity() -> usize {
602 100
603}
604
605fn default_pipeline_subscriber_capacity() -> usize {
607 10
608}
609
610fn default_concurrent_restart_limit() -> u32 {
612 5
613}
614
615fn default_true() -> bool {
617 true
618}