Skip to main content

running_process/broker/server/
spawn_wait.rs

1//! Adaptive spawn-wait timing model for broker-managed backends.
2//!
3//! This module deliberately does not touch process handles or sockets. It
4//! models the decision loop that a future `wait_for_daemon_ready` implementation
5//! will drive with real daemon-liveness and endpoint probes.
6
7use std::time::Duration;
8
9/// Default hard ceiling for waiting until a spawned daemon endpoint is ready.
10pub const DEFAULT_SPAWN_WAIT_HARD_CEILING: Duration = Duration::from_secs(60);
11
12/// Adaptive wait sequence used between daemon-ready probes.
13pub const SPAWN_WAIT_BACKOFF_SEQUENCE: [Duration; 6] = [
14    Duration::from_millis(50),
15    Duration::from_millis(100),
16    Duration::from_millis(200),
17    Duration::from_millis(500),
18    Duration::from_secs(1),
19    Duration::from_secs(2),
20];
21
22/// Policy for deciding one step of the backend daemon ready wait.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct SpawnWaitPolicy {
25    hard_ceiling: Duration,
26}
27
28impl SpawnWaitPolicy {
29    /// Create a policy with the default 60-second hard ceiling.
30    pub fn new() -> Self {
31        Self::with_hard_ceiling(DEFAULT_SPAWN_WAIT_HARD_CEILING)
32    }
33
34    /// Create a policy with an explicit hard ceiling.
35    pub fn with_hard_ceiling(hard_ceiling: Duration) -> Self {
36        Self { hard_ceiling }
37    }
38
39    /// Return the configured hard ceiling.
40    pub fn hard_ceiling(&self) -> Duration {
41        self.hard_ceiling
42    }
43
44    /// Return the adaptive backoff for a zero-based probe attempt.
45    ///
46    /// Attempts beyond the explicit sequence are capped at the final 2-second
47    /// step.
48    pub fn backoff_for_attempt(&self, attempt: usize) -> Duration {
49        let capped_index = attempt.min(SPAWN_WAIT_BACKOFF_SEQUENCE.len() - 1);
50        SPAWN_WAIT_BACKOFF_SEQUENCE[capped_index]
51    }
52
53    /// Decide what the wait loop should do after one daemon/endpoint probe.
54    pub fn decide(&self, probe: SpawnWaitProbe) -> SpawnWaitDecision {
55        if probe.endpoint_ready {
56            return SpawnWaitDecision::EndpointReady;
57        }
58
59        if !probe.daemon_alive {
60            return SpawnWaitDecision::DaemonExitedBeforeReady;
61        }
62
63        if probe.elapsed >= self.hard_ceiling {
64            return SpawnWaitDecision::Timeout {
65                hard_ceiling: self.hard_ceiling,
66            };
67        }
68
69        SpawnWaitDecision::Sleep {
70            duration: self
71                .backoff_for_attempt(probe.attempt)
72                .min(self.hard_ceiling - probe.elapsed),
73        }
74    }
75}
76
77impl Default for SpawnWaitPolicy {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83/// Observed state after one daemon-ready probe.
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct SpawnWaitProbe {
86    /// Time elapsed since the daemon process was spawned.
87    pub elapsed: Duration,
88    /// Whether the daemon process is still alive.
89    pub daemon_alive: bool,
90    /// Whether the daemon endpoint accepted a readiness probe.
91    pub endpoint_ready: bool,
92    /// Zero-based probe attempt used to select adaptive backoff.
93    pub attempt: usize,
94}
95
96impl SpawnWaitProbe {
97    /// Build a probe observation.
98    pub fn new(
99        elapsed: Duration,
100        daemon_alive: bool,
101        endpoint_ready: bool,
102        attempt: usize,
103    ) -> Self {
104        Self {
105            elapsed,
106            daemon_alive,
107            endpoint_ready,
108            attempt,
109        }
110    }
111}
112
113/// Decision returned by [`SpawnWaitPolicy`] for one wait-loop step.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub enum SpawnWaitDecision {
116    /// The endpoint is reachable, so the daemon is ready.
117    EndpointReady,
118    /// The daemon exited before its endpoint became ready.
119    DaemonExitedBeforeReady,
120    /// The hard ceiling elapsed before the endpoint became ready.
121    Timeout {
122        /// Configured hard ceiling that bounded the wait.
123        hard_ceiling: Duration,
124    },
125    /// Sleep for this duration before probing again.
126    Sleep {
127        /// Capped adaptive backoff duration.
128        duration: Duration,
129    },
130}