eggserve_core/server/
handle.rs1use std::net::SocketAddr;
27use std::time::Duration;
28
29use tokio::sync::broadcast;
30
31use crate::server::errors::{ServerError, ShutdownResult};
32use crate::server::lifecycle::Lifecycle;
33
34pub struct ServerHandle {
48 local_addr: SocketAddr,
49 shutdown_tx: broadcast::Sender<()>,
50 join: Option<tokio::task::JoinHandle<ShutdownResult>>,
51 lifecycle: std::sync::Arc<Lifecycle>,
52}
53
54impl std::fmt::Debug for ServerHandle {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.debug_struct("ServerHandle")
57 .field("local_addr", &self.local_addr)
58 .field("state", &self.lifecycle.state())
59 .finish()
60 }
61}
62
63impl ServerHandle {
64 pub(crate) fn new(
65 local_addr: SocketAddr,
66 shutdown_tx: broadcast::Sender<()>,
67 join: tokio::task::JoinHandle<ShutdownResult>,
68 lifecycle: std::sync::Arc<Lifecycle>,
69 ) -> Self {
70 Self {
71 local_addr,
72 shutdown_tx,
73 join: Some(join),
74 lifecycle,
75 }
76 }
77
78 pub fn local_addr(&self) -> SocketAddr {
82 self.local_addr
83 }
84
85 pub fn state(&self) -> crate::server::lifecycle::LifecycleState {
87 self.lifecycle.state()
88 }
89
90 pub async fn ready(&self) -> Result<(), ServerError> {
105 let state = self.lifecycle.state();
106 match state {
107 crate::server::lifecycle::LifecycleState::Running => Ok(()),
108 crate::server::lifecycle::LifecycleState::Starting => {
109 self.lifecycle.wait_ready().await;
110
111 let state = self.lifecycle.state();
113 match state {
114 crate::server::lifecycle::LifecycleState::Running => Ok(()),
115 crate::server::lifecycle::LifecycleState::Failed => {
116 Err(ServerError::Startup("server failed during startup".into()))
117 }
118 other => Err(ServerError::Config(format!(
119 "unexpected state after ready: {other}"
120 ))),
121 }
122 }
123 crate::server::lifecycle::LifecycleState::Failed => {
124 Err(ServerError::Startup("server failed during startup".into()))
125 }
126 other => Err(ServerError::Config(format!(
127 "server not ready: in {other} state"
128 ))),
129 }
130 }
131
132 pub fn shutdown(&self) {
139 let _ = self.lifecycle.drain();
141 let _ = self.shutdown_tx.send(());
143 }
144
145 pub async fn force_shutdown(
153 mut self,
154 deadline: Duration,
155 ) -> Result<ShutdownResult, ServerError> {
156 self.shutdown();
157 match tokio::time::timeout(deadline, self.wait_internal()).await {
158 Ok(()) => {
159 if let Some(join) = self.join.take() {
161 match join.await {
162 Ok(result) => Ok(result),
163 Err(e) => Err(ServerError::Accept(std::io::Error::other(format!(
164 "server task panicked: {}",
165 e
166 )))),
167 }
168 } else {
169 Ok(ShutdownResult::Clean)
170 }
171 }
172 Err(_deadline_exceeded) => {
173 if let Some(join) = self.join.take() {
174 join.abort();
175 let _ = join.await;
176 }
177 let _ = self.lifecycle.mark_stopped();
178 Ok(ShutdownResult::Forced)
179 }
180 }
181 }
182
183 pub async fn wait(mut self) -> Result<ShutdownResult, ServerError> {
189 let state = self.lifecycle.state();
191 if !state.is_terminal() {
192 self.shutdown();
193 }
194
195 self.wait_internal().await;
197
198 if let Some(join) = self.join.take() {
200 match join.await {
201 Ok(result) => Ok(result),
202 Err(e) => Err(ServerError::Accept(std::io::Error::other(format!(
203 "server task panicked: {}",
204 e
205 )))),
206 }
207 } else {
208 Ok(ShutdownResult::Clean)
209 }
210 }
211
212 async fn wait_internal(&self) {
214 let mut terminal_rx = self.lifecycle.subscribe_terminal();
216 let state = self.lifecycle.state();
217 if state.is_terminal() {
218 return;
219 }
220 let _ = terminal_rx.recv().await;
221 }
222}
223
224impl Drop for ServerHandle {
225 fn drop(&mut self) {
226 if self.join.is_some() {
228 let _ = self.lifecycle.drain();
229 let _ = self.shutdown_tx.send(());
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::server::lifecycle::Lifecycle;
238 use std::sync::Arc;
239
240 async fn make_test_handle() -> ServerHandle {
241 let lifecycle = Arc::new(Lifecycle::new());
242 let (tx, _rx) = broadcast::channel(1);
243 let join = tokio::spawn(async { ShutdownResult::Clean });
244 ServerHandle::new("127.0.0.1:8000".parse().unwrap(), tx, join, lifecycle)
245 }
246
247 fn make_handle_with_state(state: crate::server::lifecycle::LifecycleState) -> ServerHandle {
248 let lifecycle = Arc::new(Lifecycle::new());
249 match state {
250 crate::server::lifecycle::LifecycleState::Created => {}
251 crate::server::lifecycle::LifecycleState::Starting => {
252 lifecycle.start().unwrap();
253 }
254 crate::server::lifecycle::LifecycleState::Running => {
255 lifecycle.start().unwrap();
256 lifecycle.mark_running().unwrap();
257 }
258 crate::server::lifecycle::LifecycleState::Failed => {
259 lifecycle.mark_failed().unwrap();
260 }
261 crate::server::lifecycle::LifecycleState::Draining => {
262 lifecycle.start().unwrap();
263 lifecycle.mark_running().unwrap();
264 lifecycle.drain().unwrap();
265 }
266 crate::server::lifecycle::LifecycleState::Stopped => {
267 lifecycle.start().unwrap();
268 lifecycle.mark_running().unwrap();
269 lifecycle.drain().unwrap();
270 lifecycle.mark_stopped().unwrap();
271 }
272 }
273 let (shutdown_tx, _) = broadcast::channel(1);
274 let join = tokio::spawn(async { ShutdownResult::Clean });
275 ServerHandle::new("127.0.0.1:0".parse().unwrap(), shutdown_tx, join, lifecycle)
276 }
277
278 #[tokio::test]
279 async fn handle_local_addr() {
280 let handle = make_test_handle().await;
281 assert_eq!(
282 handle.local_addr(),
283 "127.0.0.1:8000".parse::<SocketAddr>().unwrap()
284 );
285 }
286
287 #[tokio::test]
288 async fn handle_state_initial() {
289 let handle = make_test_handle().await;
290 assert_eq!(
291 handle.state(),
292 crate::server::lifecycle::LifecycleState::Created
293 );
294 }
295
296 #[tokio::test]
297 async fn handle_shutdown_sends_signal() {
298 let lifecycle = Arc::new(Lifecycle::new());
299 lifecycle.start().unwrap();
301 lifecycle.mark_running().unwrap();
302
303 let (tx, mut rx) = broadcast::channel(1);
304 let join = tokio::spawn(async move {
305 let _ = rx.recv().await;
306 ShutdownResult::Clean
307 });
308 let handle = ServerHandle::new("127.0.0.1:0".parse().unwrap(), tx, join, lifecycle);
309 handle.shutdown();
310 }
312
313 #[tokio::test]
314 async fn handle_ready_returns_error_for_failed() {
315 let lifecycle = Arc::new(Lifecycle::new());
316 lifecycle.mark_failed().unwrap();
317
318 let (tx, _rx) = broadcast::channel(1);
319 let join = tokio::spawn(async { ShutdownResult::Clean });
320 let handle = ServerHandle::new("127.0.0.1:0".parse().unwrap(), tx, join, lifecycle);
321
322 let result = handle.ready().await;
323 assert!(result.is_err());
324 }
325
326 #[tokio::test]
327 async fn handle_debug_format() {
328 let handle = make_test_handle().await;
329 let debug = format!("{:?}", handle);
330 assert!(debug.contains("ServerHandle"));
331 assert!(debug.contains("127.0.0.1:8000"));
332 }
333
334 #[tokio::test]
337 async fn ready_already_running_returns_ok() {
338 let lifecycle = Arc::new(Lifecycle::new());
339 lifecycle.start().unwrap();
340 lifecycle.mark_running().unwrap();
341 assert_eq!(
342 lifecycle.state(),
343 crate::server::lifecycle::LifecycleState::Running
344 );
345
346 let (tx, _rx) = broadcast::channel(1);
347 let join = tokio::spawn(async { ShutdownResult::Clean });
348 let handle = ServerHandle::new("127.0.0.1:0".parse().unwrap(), tx, join, lifecycle);
349
350 let result = handle.ready().await;
351 assert!(
352 result.is_ok(),
353 "ready() on already-Running server: {:?}",
354 result.err()
355 );
356 }
357
358 #[tokio::test]
359 async fn ready_failed_returns_error() {
360 let handle = make_handle_with_state(crate::server::lifecycle::LifecycleState::Failed);
361 let result = handle.ready().await;
362 assert!(result.is_err());
363 }
364
365 #[tokio::test]
366 async fn ready_starting_then_running_succeeds() {
367 let lifecycle = Arc::new(Lifecycle::new());
368 lifecycle.start().unwrap();
369 let (tx, _) = broadcast::channel(1);
370 let join = tokio::spawn(async { ShutdownResult::Clean });
371 let handle = ServerHandle::new("127.0.0.1:0".parse().unwrap(), tx, join, lifecycle.clone());
372
373 tokio::spawn(async move {
375 tokio::time::sleep(Duration::from_millis(20)).await;
376 lifecycle.mark_running().unwrap();
377 });
378
379 let result = tokio::time::timeout(Duration::from_secs(5), handle.ready()).await;
380 assert!(result.is_ok());
381 assert!(result.unwrap().is_ok());
382 }
383
384 #[tokio::test]
385 async fn ready_starting_then_failed_returns_error() {
386 let lifecycle = Arc::new(Lifecycle::new());
387 lifecycle.start().unwrap();
388 let (tx, _) = broadcast::channel(1);
389 let join = tokio::spawn(async { ShutdownResult::Clean });
390 let handle = ServerHandle::new("127.0.0.1:0".parse().unwrap(), tx, join, lifecycle.clone());
391
392 tokio::spawn(async move {
394 tokio::time::sleep(Duration::from_millis(20)).await;
395 lifecycle.mark_failed().unwrap();
396 });
397
398 let result = handle.ready().await;
399 assert!(result.is_err());
400 }
401
402 #[tokio::test]
403 async fn ready_stuck_starting_times_out() {
404 let handle = make_handle_with_state(crate::server::lifecycle::LifecycleState::Starting);
405 let result = tokio::time::timeout(Duration::from_millis(50), handle.ready()).await;
406 assert!(result.is_err());
408 }
409
410 #[tokio::test]
411 async fn ready_draining_is_error() {
412 let handle = make_handle_with_state(crate::server::lifecycle::LifecycleState::Draining);
413 let result = handle.ready().await;
414 assert!(result.is_err());
415 }
416
417 #[tokio::test]
418 async fn ready_stopped_is_error() {
419 let handle = make_handle_with_state(crate::server::lifecycle::LifecycleState::Stopped);
420 let result = handle.ready().await;
421 assert!(result.is_err());
422 }
423
424 #[tokio::test]
425 async fn ready_idempotent_on_running() {
426 let handle = make_handle_with_state(crate::server::lifecycle::LifecycleState::Running);
427 let r1 = tokio::time::timeout(Duration::from_millis(50), handle.ready()).await;
429 assert!(r1.is_ok() && r1.unwrap().is_ok());
430 let r2 = tokio::time::timeout(Duration::from_millis(50), handle.ready()).await;
432 assert!(r2.is_ok() && r2.unwrap().is_ok());
433 }
434}