1use std::{
2 future::Future,
3 sync::Arc,
4 sync::atomic::{AtomicBool, Ordering},
5};
6
7use saddle_core::{ComponentLifecycle, ErrorKind, Result, SaddleError};
8
9use crate::RequestLifecycle;
10
11static RUNTIME_STARTED: AtomicBool = AtomicBool::new(false);
12const WORKER_THREADS: usize = 2;
13const MAX_IO_EVENTS_PER_TICK: usize = 5;
14
15pub struct Application {
20 components: Vec<Arc<dyn ComponentLifecycle>>,
21 requests: RequestLifecycle,
22 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
23 pending_driver_finalizer: crate::post_driver::PendingDriverFinalizerSlot,
24}
25
26impl Application {
27 pub fn new() -> Self {
29 Self {
30 components: Vec::new(),
31 requests: RequestLifecycle::new(),
32 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
33 pending_driver_finalizer: crate::post_driver::PendingDriverFinalizerSlot::new(),
34 }
35 }
36
37 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
38 pub(crate) fn install_prevalidated_components(
39 &mut self,
40 components: Vec<Arc<dyn ComponentLifecycle>>,
41 ) {
42 debug_assert!(self.components.is_empty());
43 self.components = components;
44 }
45
46 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
47 #[doc(hidden)]
48 pub fn pending_driver_finalizer(&self) -> crate::post_driver::PendingDriverFinalizerSlot {
49 self.pending_driver_finalizer.clone()
50 }
51
52 pub fn request_lifecycle(&self) -> RequestLifecycle {
54 self.requests.clone()
55 }
56
57 pub fn register<C>(&mut self, component: C) -> Result<()>
61 where
62 C: ComponentLifecycle + 'static,
63 {
64 self.register_shared(Arc::new(component))
65 }
66
67 pub fn register_shared(&mut self, component: Arc<dyn ComponentLifecycle>) -> Result<()> {
69 if self
70 .components
71 .iter()
72 .any(|registered| registered.name() == component.name())
73 {
74 return Err(SaddleError::new(
75 ErrorKind::Conflict,
76 "runtime.duplicate_component",
77 format!("component '{}' is already registered", component.name()),
78 ));
79 }
80 self.components.push(component);
81 Ok(())
82 }
83
84 pub fn run(self) -> Result<()> {
90 Self::run_with(|| async move { Ok(self) })
91 }
92
93 pub fn run_with<F, Fut>(bootstrap: F) -> Result<()>
100 where
101 F: FnOnce() -> Fut + Send + 'static,
102 Fut: Future<Output = Result<Self>> + Send + 'static,
103 {
104 if RUNTIME_STARTED
105 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
106 .is_err()
107 {
108 return Err(SaddleError::new(
109 ErrorKind::Conflict,
110 "runtime.already_started",
111 "the Saddle runtime has already started in this process",
112 ));
113 }
114
115 let runtime = build_runtime()?;
116
117 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
118 {
119 Self::run_with_owned_runtime(runtime, bootstrap)
120 }
121
122 #[cfg(not(all(target_arch = "x86_64", target_os = "linux")))]
123 runtime.block_on(async {
124 let signal = ShutdownSignal::register()?;
125 bootstrap_and_run(bootstrap, signal.wait()).await
126 })
127 }
128
129 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
130 pub(crate) fn claim_process_runtime() -> Result<()> {
131 if RUNTIME_STARTED
132 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
133 .is_err()
134 {
135 return Err(SaddleError::new(
136 ErrorKind::Conflict,
137 "runtime.already_started",
138 "the Saddle runtime has already started in this process",
139 ));
140 }
141 Ok(())
142 }
143
144 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
145 pub(crate) fn run_with_owned_runtime<F, Fut>(
146 runtime: tokio::runtime::Runtime,
147 bootstrap: F,
148 ) -> Result<()>
149 where
150 F: FnOnce() -> Fut,
151 Fut: Future<Output = Result<Self>>,
152 {
153 let outcome = runtime.block_on(async {
154 let signal = ShutdownSignal::register()?;
155 let application = bootstrap().await?;
156 let finalizer = application.pending_driver_finalizer();
157 let result = application.run_until_shutdown(signal.wait()).await;
158 Ok::<_, SaddleError>((finalizer, result))
159 });
160 match outcome {
161 Ok((finalizer, result)) => finalizer.finish(runtime, result),
162 Err(error) => {
163 drop(runtime);
164 Err(error)
165 }
166 }
167 }
168
169 pub(crate) async fn run_until_shutdown<F>(self, shutdown: F) -> Result<()>
170 where
171 F: Future<Output = Result<()>>,
172 {
173 tokio::pin!(shutdown);
174 let signal_before_start = tokio::select! {
175 biased;
176 signal_result = &mut shutdown => Some(signal_result),
177 _ = std::future::ready(()) => None,
178 };
179 if let Some(signal_result) = signal_before_start {
180 self.requests.begin_draining();
181 self.requests.wait_until_drained().await;
182 self.requests.mark_stopped();
183 return signal_result;
184 }
185
186 let mut started = 0;
187
188 for component in &self.components {
189 let start = component.start();
190 tokio::pin!(start);
191 let mut shutdown_during_start = None;
192 let start_result = tokio::select! {
193 biased;
194 signal_result = &mut shutdown => {
195 shutdown_during_start = Some(signal_result);
196 start.await
200 }
201 start_result = &mut start => start_result,
202 };
203
204 if let Err(error) = start_result {
205 self.requests.begin_draining();
206 self.requests.wait_until_drained().await;
207 let _ = self.shutdown_components(started).await;
208 self.requests.mark_stopped();
209 return Err(error);
210 }
211 started += 1;
212
213 if let Some(signal_result) = shutdown_during_start {
214 self.requests.begin_draining();
215 self.requests.wait_until_drained().await;
216 let shutdown_result = self.shutdown_components(started).await;
217 self.requests.mark_stopped();
218 return signal_result.and(shutdown_result);
219 }
220 }
221
222 self.requests.mark_ready();
223 let signal_result = shutdown.await;
224 self.requests.begin_draining();
225 self.requests.wait_until_drained().await;
226 let shutdown_result = self.shutdown_components(started).await;
227 self.requests.mark_stopped();
228
229 signal_result.and(shutdown_result)
230 }
231
232 async fn shutdown_components(&self, started: usize) -> Result<()> {
233 let mut first_error = None;
234 for component in self.components[..started].iter().rev() {
235 if let Err(error) = component.shutdown().await {
236 if first_error.is_none() {
237 first_error = Some(error);
238 }
239 }
240 }
241 first_error.map_or(Ok(()), Err)
242 }
243}
244
245#[cfg(any(test, not(all(target_arch = "x86_64", target_os = "linux"))))]
246async fn bootstrap_and_run<F, Fut, S>(bootstrap: F, shutdown: S) -> Result<()>
247where
248 F: FnOnce() -> Fut,
249 Fut: Future<Output = Result<Application>>,
250 S: Future<Output = Result<()>>,
251{
252 let application = bootstrap().await?;
253 application.run_until_shutdown(shutdown).await
254}
255
256impl Default for Application {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262fn build_runtime() -> Result<tokio::runtime::Runtime> {
263 tokio::runtime::Builder::new_multi_thread()
264 .worker_threads(WORKER_THREADS)
265 .max_io_events_per_tick(MAX_IO_EVENTS_PER_TICK)
266 .enable_all()
267 .build()
268 .map_err(|_| {
269 SaddleError::new(
270 ErrorKind::Infrastructure,
271 "runtime.initialization_failed",
272 "failed to initialize the Saddle async runtime",
273 )
274 })
275}
276
277#[cfg(unix)]
278pub(crate) struct ShutdownSignal {
279 interrupt: tokio::signal::unix::Signal,
280 terminate: tokio::signal::unix::Signal,
281}
282
283#[cfg(unix)]
284impl ShutdownSignal {
285 pub(crate) fn register() -> Result<Self> {
287 Ok(Self {
288 interrupt: tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
289 .map_err(|_| signal_error())?,
290 terminate: tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
291 .map_err(|_| signal_error())?,
292 })
293 }
294
295 pub(crate) async fn wait(mut self) -> Result<()> {
296 tokio::select! {
297 _ = self.interrupt.recv() => Ok(()),
298 _ = self.terminate.recv() => Ok(()),
299 }
300 }
301}
302
303#[cfg(windows)]
304struct ShutdownSignal {
305 ctrl_c: tokio::signal::windows::CtrlC,
306 ctrl_break: tokio::signal::windows::CtrlBreak,
307}
308
309#[cfg(windows)]
310impl ShutdownSignal {
311 fn register() -> Result<Self> {
313 Ok(Self {
314 ctrl_c: tokio::signal::windows::ctrl_c().map_err(|_| signal_error())?,
315 ctrl_break: tokio::signal::windows::ctrl_break().map_err(|_| signal_error())?,
316 })
317 }
318
319 async fn wait(mut self) -> Result<()> {
320 tokio::select! {
321 _ = self.ctrl_c.recv() => Ok(()),
322 _ = self.ctrl_break.recv() => Ok(()),
323 }
324 }
325}
326
327fn signal_error() -> SaddleError {
328 SaddleError::new(
329 ErrorKind::Infrastructure,
330 "runtime.signal_registration_failed",
331 "failed to register the application shutdown signal",
332 )
333}
334
335#[cfg(test)]
336mod tests {
337 use std::sync::Mutex;
338
339 use saddle_core::LifecycleFuture;
340
341 use super::*;
342
343 struct RecordingComponent {
344 name: &'static str,
345 events: Arc<Mutex<Vec<String>>>,
346 start_error: bool,
347 shutdown_error: bool,
348 }
349
350 struct BlockingStartComponent {
351 events: Arc<Mutex<Vec<String>>>,
352 started: Mutex<Option<tokio::sync::oneshot::Sender<()>>>,
353 release: Mutex<Option<tokio::sync::oneshot::Receiver<()>>>,
354 }
355
356 impl ComponentLifecycle for RecordingComponent {
357 fn name(&self) -> &'static str {
358 self.name
359 }
360
361 fn start(&self) -> LifecycleFuture<'_> {
362 Box::pin(async move {
363 self.events
364 .lock()
365 .unwrap()
366 .push(format!("start:{}", self.name));
367 if self.start_error {
368 Err(test_error("start failed"))
369 } else {
370 Ok(())
371 }
372 })
373 }
374
375 fn shutdown(&self) -> LifecycleFuture<'_> {
376 Box::pin(async move {
377 self.events
378 .lock()
379 .unwrap()
380 .push(format!("shutdown:{}", self.name));
381 if self.shutdown_error {
382 Err(test_error("shutdown failed"))
383 } else {
384 Ok(())
385 }
386 })
387 }
388 }
389
390 impl ComponentLifecycle for BlockingStartComponent {
391 fn name(&self) -> &'static str {
392 "blocking"
393 }
394
395 fn start(&self) -> LifecycleFuture<'_> {
396 Box::pin(async move {
397 self.events
398 .lock()
399 .unwrap()
400 .push("start:blocking".to_owned());
401 let started = self.started.lock().unwrap().take().unwrap();
402 let release = self.release.lock().unwrap().take().unwrap();
403 started.send(()).unwrap();
404 release.await.unwrap();
405 Ok(())
406 })
407 }
408
409 fn shutdown(&self) -> LifecycleFuture<'_> {
410 Box::pin(async move {
411 self.events
412 .lock()
413 .unwrap()
414 .push("shutdown:blocking".to_owned());
415 Ok(())
416 })
417 }
418 }
419
420 fn component(name: &'static str, events: &Arc<Mutex<Vec<String>>>) -> RecordingComponent {
421 RecordingComponent {
422 name,
423 events: Arc::clone(events),
424 start_error: false,
425 shutdown_error: false,
426 }
427 }
428
429 fn test_error(message: &'static str) -> SaddleError {
430 SaddleError::new(ErrorKind::Infrastructure, "test.failure", message)
431 }
432
433 fn test_runtime() -> tokio::runtime::Runtime {
434 tokio::runtime::Builder::new_current_thread()
435 .build()
436 .expect("test runtime must build")
437 }
438
439 async fn shutdown_when_ready(requests: RequestLifecycle) -> Result<()> {
440 while requests.phase() != crate::ApplicationPhase::Ready {
441 tokio::task::yield_now().await;
442 }
443 Ok(())
444 }
445
446 #[test]
447 fn components_start_in_order_and_shutdown_in_reverse() {
448 let events = Arc::new(Mutex::new(Vec::new()));
449 let mut application = Application::new();
450 application.register(component("db", &events)).unwrap();
451 application.register(component("service", &events)).unwrap();
452 let shutdown = shutdown_when_ready(application.request_lifecycle());
453
454 test_runtime()
455 .block_on(application.run_until_shutdown(shutdown))
456 .unwrap();
457
458 assert_eq!(
459 *events.lock().unwrap(),
460 [
461 "start:db",
462 "start:service",
463 "shutdown:service",
464 "shutdown:db"
465 ]
466 );
467 }
468
469 #[test]
470 fn async_bootstrap_runs_before_early_shutdown_prevents_component_start() {
471 let events = Arc::new(Mutex::new(Vec::new()));
472 let bootstrap_events = Arc::clone(&events);
473
474 test_runtime()
475 .block_on(bootstrap_and_run(
476 move || async move {
477 bootstrap_events
478 .lock()
479 .unwrap()
480 .push("bootstrap".to_owned());
481 let mut application = Application::new();
482 application.register(component("component", &bootstrap_events))?;
483 Ok(application)
484 },
485 std::future::ready(Ok(())),
486 ))
487 .unwrap();
488
489 assert_eq!(*events.lock().unwrap(), ["bootstrap"]);
490 }
491
492 #[test]
493 fn failed_async_bootstrap_does_not_start_components() {
494 let error = test_runtime()
495 .block_on(bootstrap_and_run(
496 || async { Err(test_error("bootstrap failed")) },
497 std::future::pending(),
498 ))
499 .unwrap_err();
500 assert_eq!(error.message(), "bootstrap failed");
501 }
502
503 #[test]
504 fn startup_failure_rolls_back_only_started_components() {
505 let events = Arc::new(Mutex::new(Vec::new()));
506 let mut application = Application::new();
507 application.register(component("first", &events)).unwrap();
508 let mut failing = component("failing", &events);
509 failing.start_error = true;
510 application.register(failing).unwrap();
511 application.register(component("never", &events)).unwrap();
512 let shutdown = shutdown_when_ready(application.request_lifecycle());
513
514 let error = test_runtime()
515 .block_on(application.run_until_shutdown(shutdown))
516 .unwrap_err();
517
518 assert_eq!(error.message(), "start failed");
519 assert_eq!(
520 *events.lock().unwrap(),
521 ["start:first", "start:failing", "shutdown:first"]
522 );
523 }
524
525 #[test]
526 fn shutdown_continues_after_a_component_error() {
527 let events = Arc::new(Mutex::new(Vec::new()));
528 let mut application = Application::new();
529 application.register(component("first", &events)).unwrap();
530 let mut failing = component("second", &events);
531 failing.shutdown_error = true;
532 application.register(failing).unwrap();
533 let shutdown = shutdown_when_ready(application.request_lifecycle());
534
535 let error = test_runtime()
536 .block_on(application.run_until_shutdown(shutdown))
537 .unwrap_err();
538
539 assert_eq!(error.message(), "shutdown failed");
540 assert_eq!(
541 *events.lock().unwrap(),
542 [
543 "start:first",
544 "start:second",
545 "shutdown:second",
546 "shutdown:first"
547 ]
548 );
549 }
550
551 #[test]
552 fn duplicate_component_names_are_rejected() {
553 let events = Arc::new(Mutex::new(Vec::new()));
554 let mut application = Application::new();
555 application.register(component("db", &events)).unwrap();
556
557 let error = application.register(component("db", &events)).unwrap_err();
558 assert_eq!(error.code(), "runtime.duplicate_component");
559 }
560
561 #[test]
562 fn application_shutdown_waits_for_an_admitted_request() {
563 test_runtime().block_on(async {
564 let application = Application::new();
565 let requests = application.request_lifecycle();
566 let (release, released) = tokio::sync::oneshot::channel();
567
568 let shutdown = async move {
569 shutdown_when_ready(requests.clone()).await?;
570 let request = requests
571 .try_accept()
572 .expect("application is ready before waiting for shutdown");
573 tokio::spawn(async move {
574 released.await.unwrap();
575 drop(request);
576 });
577 Ok(())
578 };
579 let running = tokio::spawn(application.run_until_shutdown(shutdown));
580
581 tokio::task::yield_now().await;
582 assert!(!running.is_finished());
583 release.send(()).unwrap();
584 running.await.unwrap().unwrap();
585 });
586 }
587
588 #[test]
589 fn signal_failure_before_start_prevents_component_startup() {
590 let events = Arc::new(Mutex::new(Vec::new()));
591 let mut application = Application::new();
592 application.register(component("service", &events)).unwrap();
593
594 let error = test_runtime()
595 .block_on(application.run_until_shutdown(async { Err(signal_error()) }))
596 .unwrap_err();
597
598 assert_eq!(error.code(), "runtime.signal_registration_failed");
599 assert!(events.lock().unwrap().is_empty());
600 }
601
602 #[test]
603 fn shutdown_during_startup_stops_starting_and_rolls_back() {
604 test_runtime().block_on(async {
605 let events = Arc::new(Mutex::new(Vec::new()));
606 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
607 let (release_tx, release_rx) = tokio::sync::oneshot::channel();
608 let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
609 let mut application = Application::new();
610 application
611 .register(BlockingStartComponent {
612 events: Arc::clone(&events),
613 started: Mutex::new(Some(started_tx)),
614 release: Mutex::new(Some(release_rx)),
615 })
616 .unwrap();
617 application.register(component("never", &events)).unwrap();
618
619 let running = tokio::spawn(application.run_until_shutdown(async move {
620 shutdown_rx.await.unwrap();
621 Ok(())
622 }));
623 started_rx.await.unwrap();
624 shutdown_tx.send(()).unwrap();
625 tokio::task::yield_now().await;
626 release_tx.send(()).unwrap();
627
628 running.await.unwrap().unwrap();
629 assert_eq!(
630 *events.lock().unwrap(),
631 ["start:blocking", "shutdown:blocking"]
632 );
633 });
634 }
635
636 #[test]
637 fn managed_runtime_provides_an_async_io_driver() {
638 build_runtime()
639 .unwrap()
640 .block_on(async {
641 tokio::net::TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 0)).await
642 })
643 .expect("service listeners require the managed async I/O driver");
644 }
645}