Skip to main content

a3s_boot/app/
handle.rs

1#[cfg(feature = "shutdown-hooks")]
2use super::shutdown::{normalize_shutdown_signals, wait_for_shutdown_signal, ShutdownSignal};
3use super::BootApplication;
4use crate::{BoxFuture, HttpAdapter, MessageTransport, ModuleRef, Result};
5#[cfg(feature = "shutdown-hooks")]
6use std::future::Future;
7use std::net::SocketAddr;
8use std::sync::Arc;
9
10/// Managed application with idempotent startup and shutdown.
11pub struct BootApplicationHandle {
12    app: BootApplication,
13    initialized: bool,
14    microservices: Vec<Box<dyn ConnectedMicroservice>>,
15    #[cfg(feature = "shutdown-hooks")]
16    shutdown_signals: Option<Vec<ShutdownSignal>>,
17}
18
19impl BootApplicationHandle {
20    pub fn from_app(app: BootApplication) -> Self {
21        Self {
22            app,
23            initialized: false,
24            microservices: Vec::new(),
25            #[cfg(feature = "shutdown-hooks")]
26            shutdown_signals: None,
27        }
28    }
29
30    pub fn app(&self) -> &BootApplication {
31        &self.app
32    }
33
34    pub fn module_ref(&self) -> &ModuleRef {
35        self.app.module_ref()
36    }
37
38    pub fn into_app(self) -> BootApplication {
39        self.app
40    }
41
42    pub fn get<T>(&self) -> Result<Arc<T>>
43    where
44        T: Send + Sync + 'static,
45    {
46        self.app.get::<T>()
47    }
48
49    pub fn get_named<T>(&self, token: &str) -> Result<Arc<T>>
50    where
51        T: Send + Sync + 'static,
52    {
53        self.app.get_named::<T>(token)
54    }
55
56    pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>>
57    where
58        T: Send + Sync + 'static,
59    {
60        self.app.get_optional::<T>()
61    }
62
63    pub fn get_optional_named<T>(&self, token: &str) -> Result<Option<Arc<T>>>
64    where
65        T: Send + Sync + 'static,
66    {
67        self.app.get_optional_named::<T>(token)
68    }
69
70    pub fn is_initialized(&self) -> bool {
71        self.initialized
72    }
73
74    #[cfg(feature = "shutdown-hooks")]
75    pub fn enable_shutdown_hooks<I>(&mut self, signals: I) -> &mut Self
76    where
77        I: IntoIterator<Item = ShutdownSignal>,
78    {
79        self.shutdown_signals = Some(normalize_shutdown_signals(signals));
80        self
81    }
82
83    #[cfg(feature = "shutdown-hooks")]
84    pub fn enable_default_shutdown_hooks(&mut self) -> &mut Self {
85        self.enable_shutdown_hooks(ShutdownSignal::default_signals())
86    }
87
88    pub async fn init(&mut self) -> Result<()> {
89        if self.initialized {
90            return Ok(());
91        }
92
93        if let Err(error) = self.app.bootstrap().await {
94            let _ = self.app.shutdown().await;
95            return Err(error);
96        }
97
98        self.initialized = true;
99        Ok(())
100    }
101
102    pub async fn close(&mut self) -> Result<()> {
103        self.close_inner(None).await
104    }
105
106    pub async fn close_with_signal(&mut self, signal: impl Into<String>) -> Result<()> {
107        self.close_inner(Some(signal.into())).await
108    }
109
110    async fn close_inner(&mut self, signal: Option<String>) -> Result<()> {
111        if !self.initialized {
112            return Ok(());
113        }
114
115        match signal {
116            Some(signal) => self.app.shutdown_with_signal(signal).await?,
117            None => self.app.shutdown().await?,
118        }
119        self.initialized = false;
120        Ok(())
121    }
122
123    pub async fn listen_with<A>(&mut self, adapter: &A, addr: SocketAddr) -> Result<()>
124    where
125        A: HttpAdapter,
126    {
127        #[cfg(feature = "shutdown-hooks")]
128        if let Some(signals) = self.shutdown_signals.clone() {
129            return self
130                .listen_with_shutdown_signal_future(
131                    adapter,
132                    addr,
133                    wait_for_shutdown_signal(signals),
134                )
135                .await;
136        }
137
138        self.init().await?;
139        let serve_result = adapter.serve(self.app.clone(), addr).await;
140        let close_result = self.close().await;
141
142        match (serve_result, close_result) {
143            (Err(error), _) => Err(error),
144            (Ok(()), Err(error)) => Err(error),
145            (Ok(()), Ok(())) => Ok(()),
146        }
147    }
148
149    #[cfg(feature = "shutdown-hooks")]
150    pub(crate) async fn listen_with_shutdown_signal_future<A, F>(
151        &mut self,
152        adapter: &A,
153        addr: SocketAddr,
154        signal_future: F,
155    ) -> Result<()>
156    where
157        A: HttpAdapter,
158        F: Future<Output = Result<ShutdownSignal>> + Send,
159    {
160        self.init().await?;
161        let serve_future = adapter.serve(self.app.clone(), addr);
162        futures_util::pin_mut!(signal_future);
163        futures_util::pin_mut!(serve_future);
164
165        tokio::select! {
166            serve_result = &mut serve_future => {
167                let close_result = self.close().await;
168                match (serve_result, close_result) {
169                    (Err(error), _) => Err(error),
170                    (Ok(()), Err(error)) => Err(error),
171                    (Ok(()), Ok(())) => Ok(()),
172                }
173            }
174            signal_result = &mut signal_future => {
175                match signal_result {
176                    Ok(signal) => self.close_with_signal(signal.as_str()).await,
177                    Err(error) => {
178                        let _ = self.close().await;
179                        Err(error)
180                    }
181                }
182            }
183        }
184    }
185
186    pub fn connect_microservice<T>(&mut self, transport: T) -> usize
187    where
188        T: MessageTransport + Send + Sync + 'static,
189    {
190        let index = self.microservices.len();
191        self.microservices
192            .push(Box::new(ConnectedMessageTransport { transport }));
193        index
194    }
195
196    pub fn connected_microservice_count(&self) -> usize {
197        self.microservices.len()
198    }
199
200    pub async fn start_all_microservices(&mut self) -> Result<()> {
201        self.init().await?;
202        for microservice in &self.microservices {
203            microservice.serve(self.app.clone()).await?;
204        }
205        Ok(())
206    }
207}
208
209trait ConnectedMicroservice: Send + Sync {
210    fn serve(&self, app: BootApplication) -> BoxFuture<'static, Result<()>>;
211}
212
213struct ConnectedMessageTransport<T> {
214    transport: T,
215}
216
217impl<T> ConnectedMicroservice for ConnectedMessageTransport<T>
218where
219    T: MessageTransport + Send + Sync + 'static,
220{
221    fn serve(&self, app: BootApplication) -> BoxFuture<'static, Result<()>> {
222        self.transport.serve(app)
223    }
224}