1use std::sync::atomic::{AtomicU8, Ordering};
32
33use tokio::sync::{broadcast, watch};
34
35#[repr(u8)]
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum LifecycleState {
43 Created = 0,
45 Starting = 1,
47 Running = 2,
49 Draining = 3,
51 Stopped = 4,
53 Failed = 5,
55}
56
57impl LifecycleState {
58 fn from_u8(v: u8) -> Self {
60 match v {
61 0 => Self::Created,
62 1 => Self::Starting,
63 2 => Self::Running,
64 3 => Self::Draining,
65 4 => Self::Stopped,
66 5 => Self::Failed,
67 _ => Self::Failed,
68 }
69 }
70
71 pub fn is_terminal(self) -> bool {
73 matches!(self, Self::Stopped | Self::Failed)
74 }
75}
76
77impl std::fmt::Display for LifecycleState {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 Self::Created => write!(f, "created"),
81 Self::Starting => write!(f, "starting"),
82 Self::Running => write!(f, "running"),
83 Self::Draining => write!(f, "draining"),
84 Self::Stopped => write!(f, "stopped"),
85 Self::Failed => write!(f, "failed"),
86 }
87 }
88}
89
90#[derive(Debug)]
92pub(crate) struct Lifecycle {
93 state: AtomicU8,
94 ready_tx: watch::Sender<bool>,
95 terminal_tx: broadcast::Sender<()>,
97}
98
99impl Default for Lifecycle {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105impl Lifecycle {
106 pub(crate) fn new() -> Self {
108 let (ready_tx, _) = watch::channel(false);
109 let (terminal_tx, _) = broadcast::channel(1);
110 Self {
111 state: AtomicU8::new(LifecycleState::Created as u8),
112 ready_tx,
113 terminal_tx,
114 }
115 }
116
117 pub(crate) fn state(&self) -> LifecycleState {
119 LifecycleState::from_u8(self.state.load(Ordering::Acquire))
120 }
121
122 pub(crate) fn start(&self) -> Result<(), crate::server::errors::ServerError> {
124 let prev = self.state.compare_exchange(
125 LifecycleState::Created as u8,
126 LifecycleState::Starting as u8,
127 Ordering::AcqRel,
128 Ordering::Acquire,
129 );
130 match prev {
131 Ok(_) => Ok(()),
132 Err(actual) => {
133 let state = LifecycleState::from_u8(actual);
134 if matches!(state, LifecycleState::Running | LifecycleState::Starting) {
135 Err(crate::server::errors::ServerError::AlreadyStarted)
136 } else {
137 Err(crate::server::errors::ServerError::Config(format!(
138 "cannot start: server is in {} state",
139 state
140 )))
141 }
142 }
143 }
144 }
145
146 pub(crate) fn mark_running(&self) -> Result<(), crate::server::errors::ServerError> {
148 let prev = self.state.compare_exchange(
149 LifecycleState::Starting as u8,
150 LifecycleState::Running as u8,
151 Ordering::AcqRel,
152 Ordering::Acquire,
153 );
154 match prev {
155 Ok(_) => {
156 let _ = self.ready_tx.send(true);
157 Ok(())
158 }
159 Err(actual) => Err(crate::server::errors::ServerError::Config(format!(
160 "cannot mark running: server is in {} state",
161 LifecycleState::from_u8(actual)
162 ))),
163 }
164 }
165
166 pub(crate) fn drain(&self) -> Result<(), crate::server::errors::ServerError> {
168 let prev = self.state.compare_exchange(
169 LifecycleState::Running as u8,
170 LifecycleState::Draining as u8,
171 Ordering::AcqRel,
172 Ordering::Acquire,
173 );
174 match prev {
175 Ok(_) => {
176 crate::ops::Logger::global().emit(crate::ops::Event::new(
177 crate::ops::Severity::Info,
178 crate::ops::EventKind::DrainingStarted,
179 "draining in-flight connections",
180 ));
181 Ok(())
182 }
183 Err(actual) => {
184 let state = LifecycleState::from_u8(actual);
185 if state == LifecycleState::Created || state == LifecycleState::Starting {
186 if self
188 .state
189 .compare_exchange(
190 actual,
191 LifecycleState::Stopped as u8,
192 Ordering::AcqRel,
193 Ordering::Acquire,
194 )
195 .is_ok()
196 {
197 let _ = self.terminal_tx.send(());
198 Ok(())
199 } else {
200 Err(crate::server::errors::ServerError::Config(
201 "server state changed while shutting down".into(),
202 ))
203 }
204 } else if state.is_terminal() {
205 Ok(())
206 } else {
207 Err(crate::server::errors::ServerError::Config(format!(
208 "cannot drain: server is in {} state",
209 state
210 )))
211 }
212 }
213 }
214 }
215
216 pub(crate) fn mark_stopped(&self) -> Result<(), crate::server::errors::ServerError> {
218 let prev = self.state.compare_exchange(
219 LifecycleState::Draining as u8,
220 LifecycleState::Stopped as u8,
221 Ordering::AcqRel,
222 Ordering::Acquire,
223 );
224 match prev {
225 Ok(_) => {
226 let _ = self.terminal_tx.send(());
227 Ok(())
228 }
229 Err(actual) => {
230 let state = LifecycleState::from_u8(actual);
231 if state.is_terminal() {
232 Ok(())
233 } else {
234 Err(crate::server::errors::ServerError::Config(format!(
235 "cannot stop: server is in {} state",
236 state
237 )))
238 }
239 }
240 }
241 }
242
243 #[allow(dead_code)]
248 pub(crate) fn mark_failed(&self) -> Result<(), crate::server::errors::ServerError> {
249 let current = self.state.load(Ordering::Acquire);
250 let current_state = LifecycleState::from_u8(current);
251 if current_state.is_terminal() {
252 return Ok(());
253 }
254 self.state
255 .store(LifecycleState::Failed as u8, Ordering::Release);
256 let _ = self.ready_tx.send(true);
259 let _ = self.terminal_tx.send(());
260 Ok(())
261 }
262
263 pub(crate) async fn wait_ready(&self) {
265 let mut rx = self.ready_tx.subscribe();
266 if *rx.borrow() {
268 return;
269 }
270 let _ = rx.changed().await;
271 }
272
273 pub(crate) fn subscribe_terminal(&self) -> broadcast::Receiver<()> {
275 self.terminal_tx.subscribe()
276 }
277
278 #[allow(dead_code)]
280 pub(crate) fn is(&self, expected: LifecycleState) -> bool {
281 self.state() == expected
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn initial_state_is_created() {
291 let lc = Lifecycle::new();
292 assert_eq!(lc.state(), LifecycleState::Created);
293 assert!(!lc.state().is_terminal());
294 }
295
296 #[test]
297 fn valid_transitions() {
298 let lc = Lifecycle::new();
299 assert!(lc.start().is_ok());
300 assert_eq!(lc.state(), LifecycleState::Starting);
301
302 assert!(lc.mark_running().is_ok());
303 assert_eq!(lc.state(), LifecycleState::Running);
304
305 assert!(lc.drain().is_ok());
306 assert_eq!(lc.state(), LifecycleState::Draining);
307
308 assert!(lc.mark_stopped().is_ok());
309 assert_eq!(lc.state(), LifecycleState::Stopped);
310 assert!(lc.state().is_terminal());
311 }
312
313 #[test]
314 fn double_start_fails() {
315 let lc = Lifecycle::new();
316 assert!(lc.start().is_ok());
317 assert!(lc.mark_running().is_ok());
318 let err = lc.start().unwrap_err();
319 assert!(err.to_string().contains("already started"));
320 }
321
322 #[test]
323 fn shutdown_before_start_stops_lifecycle() {
324 let lc = Lifecycle::new();
325 assert!(lc.drain().is_ok());
326 assert_eq!(lc.state(), LifecycleState::Stopped);
327 }
328
329 #[test]
330 fn mark_failed_from_any_non_terminal() {
331 let lc = Lifecycle::new();
332 assert!(lc.mark_failed().is_ok());
333 assert_eq!(lc.state(), LifecycleState::Failed);
334 assert!(lc.state().is_terminal());
335 }
336
337 #[test]
338 fn mark_stopped_from_already_stopped_is_ok() {
339 let lc = Lifecycle::new();
340 assert!(lc.start().is_ok());
341 assert!(lc.mark_running().is_ok());
342 assert!(lc.drain().is_ok());
343 assert!(lc.mark_stopped().is_ok());
344 assert!(lc.mark_stopped().is_ok());
345 }
346
347 #[test]
348 fn lifecycle_state_display() {
349 assert_eq!(LifecycleState::Created.to_string(), "created");
350 assert_eq!(LifecycleState::Running.to_string(), "running");
351 assert_eq!(LifecycleState::Draining.to_string(), "draining");
352 assert_eq!(LifecycleState::Stopped.to_string(), "stopped");
353 assert_eq!(LifecycleState::Failed.to_string(), "failed");
354 }
355}