Skip to main content

harmos_runtime/runtime/
launch.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Application Preparation And Retained Lifecycle Ownership.
11
12/* === Modules === */
13
14/* === Re-Exports === */
15
16/* === Imports === */
17
18// Standard Library Imports
19use std::future::Future;
20use std::panic::AssertUnwindSafe;
21use std::sync::{Arc, Mutex};
22
23// Third Party Imports
24use futures_util::FutureExt;
25
26// Local Imports
27use super::{Builder, Runtime, StartupError};
28use crate::operations::{Lane, LaneSettings};
29use crate::supervision::cleanup::{Cleanup, Finalize, Resource};
30use crate::{Application, Error, Setup, Startup};
31
32/* === Constants === */
33
34/* === Macros === */
35
36/* === Type Aliases === */
37
38type Cause = Box<dyn std::error::Error + Send + Sync>;
39
40/* === Structs === */
41
42pub(super) struct ApplicationCleanup<A: Application> {
43    owner: Arc<Cleanup<Owner<A>>>,
44    failure: Arc<Mutex<Option<Arc<Error>>>>,
45}
46
47struct Preparation {
48    cleanup: super::BootstrapCleanup,
49    armed: bool,
50}
51
52struct Owner<A: Application> {
53    application: A,
54    context: Option<Startup<A>>,
55    initialized: bool,
56    stop_attempted: bool,
57    failure: Arc<Mutex<Option<Arc<Error>>>>,
58}
59
60/* === Enums === */
61
62#[derive(Debug, thiserror::Error)]
63enum LifecycleFailure {
64    #[error("application {phase} failed: {source}")]
65    Callback {
66        phase: &'static str,
67
68        #[source]
69        source: Cause,
70    },
71
72    #[error("application {phase} panicked")]
73    Panicked { phase: &'static str },
74}
75
76/* === Traits === */
77
78/* === Implementations === */
79
80impl Drop for Preparation {
81    fn drop(&mut self) {
82        if self.armed {
83            self.cleanup.settle_unowned();
84        }
85    }
86}
87
88impl<A: Application> Runtime<A> {
89    pub async fn launch(origin: impl Into<crate::state::Origin<A::State>>, config: A::Config) -> Result<Self, StartupError> {
90        let launch = Self::launch_with(origin, config, |_| {});
91        Box::pin(launch).await
92    }
93
94    pub async fn launch_with(
95        origin: impl Into<crate::state::Origin<A::State>>, config: A::Config, overrides: impl FnOnce(&mut Setup<A>),
96    ) -> Result<Self, StartupError> {
97        let mut setup = Setup::<A>::new(Self::builder(origin));
98        let mut preparation = Preparation {
99            cleanup: setup.settings().startup_cleanup(),
100            armed: true,
101        };
102        let prepared = guarded("prepare", A::prepare(config, &mut setup)).await;
103        if prepared.is_ok() {
104            preparation.armed = false;
105        }
106        setup.overrides(overrides);
107        let validation = setup.validate();
108        let mut errors = Vec::new();
109        if let Err(error) = validation {
110            errors.push(error);
111        }
112        let application = match prepared {
113            Ok(application) => application,
114            Err(source) => {
115                errors.push(StartupError::Runtime { source });
116                return Err(StartupError::aggregate(errors).expect("prepare failed"));
117            }
118        };
119        if let Some(error) = StartupError::aggregate(errors) {
120            let receipt = setup.settings().startup_cleanup();
121            let mut rejected = super::boot::Boot::new(None, receipt).expect("no storage claim before setup validation");
122            rejected.application = Some(ApplicationCleanup::new(application, None));
123            let rejection = rejected.reject(super::Incompatible::work(&error)).await;
124            if rejection.cleanup.is_some() {
125                return Err(StartupError::Multiple {
126                    errors: vec![error, StartupError::Runtime { source: Box::new(rejection) }],
127                });
128            }
129            return Err(error);
130        }
131        let mut builder = setup.into_configuration();
132        builder.application = Some(application);
133        builder.runtime().await
134    }
135}
136
137impl<A: Application> harmos_sdk::lifecycle::SetupConfiguration for Builder<A> {
138    fn resource<T: Send + Sync + 'static>(&mut self, value: Arc<T>, replace: bool) {
139        if replace {
140            self.resources.replace(value);
141            return;
142        }
143        self.resource::<T>(value);
144    }
145
146    fn validate(&mut self) -> Result<(), StartupError> {
147        self.validate_configuration()
148    }
149}
150
151impl<A: Application> harmos_sdk::lifecycle::ApplicationConfiguration for Builder<A> {
152    type Lane = A::Lanes;
153    type LaneSettings = LaneSettings;
154
155    fn lane(&mut self, lane: A::Lanes, settings: LaneSettings) {
156        if !self.lanes.configure(lane.name(), settings.capacity) {
157            self.configuration_errors.push(StartupError::UnknownLane { lane: lane.name() });
158        }
159    }
160
161    fn operations<C: harmos_sdk::operations::OperationCatalog>(&mut self, lane: A::Lanes) {
162        self.operations::<C>(lane);
163    }
164}
165
166impl<A: Application> Clone for ApplicationCleanup<A> {
167    fn clone(&self) -> Self {
168        Self {
169            owner: self.owner.clone(),
170            failure: self.failure.clone(),
171        }
172    }
173}
174
175impl<A: Application> ApplicationCleanup<A> {
176    pub(super) fn new(application: A, context: Option<Startup<A>>) -> Self {
177        let failure = Arc::new(Mutex::new(None));
178        let owner = Owner {
179            application,
180            context,
181            initialized: false,
182            stop_attempted: false,
183            failure: failure.clone(),
184        };
185        Self {
186            owner: Arc::new(Cleanup::new(owner)),
187            failure,
188        }
189    }
190
191    pub(super) async fn initialize(&self, context: Startup<A>) -> Result<(), Cause> {
192        let mut original = self.owner.take().expect("initialization owns its application");
193        let owner = original.resource();
194        owner.context = Some(context);
195        owner.initialized = true;
196        let context = owner.context.as_ref().expect("runtime startup supplies its context");
197        guarded("initialize", owner.application.initialize(context)).await
198    }
199
200    pub(super) async fn settle(&self) -> Result<(), Arc<Error>> {
201        self.owner.settle().await
202    }
203
204    pub(super) fn failure(&self) -> Option<Arc<Error>> {
205        self.failure.lock().expect("application lifecycle outcome is healthy").clone()
206    }
207}
208
209impl<A: Application> Resource for Owner<A> {
210    fn finalize(&mut self) -> Finalize<'_> {
211        Box::pin(async move {
212            if self.initialized && !self.stop_attempted {
213                self.stop_attempted = true;
214                let context = self.context.as_ref().expect("initialized application retains its context");
215                if let Err(source) = guarded("stop", self.application.stop(context)).await {
216                    let error = Error::TaskFailed {
217                        domain: "application stop",
218                        source: Arc::from(source),
219                    };
220                    *self.failure.lock().expect("application lifecycle outcome is healthy") = Some(Arc::new(error));
221                }
222            }
223            let cleanup = guarded("cleanup", self.application.cleanup()).await.map_err(|source| Error::TaskFailed {
224                domain: "application cleanup",
225                source: Arc::from(source),
226            });
227            let Err(cleanup) = cleanup else { return Ok(()) };
228            let stop = self.failure.lock().expect("application lifecycle outcome is healthy").clone();
229            let error = match stop {
230                Some(stop) => Error::ApplicationLifecycle { stop, cleanup: Arc::new(cleanup) },
231                None => cleanup,
232            };
233            Err(Arc::new(error))
234        })
235    }
236}
237
238/* === Public Functions === */
239
240/* === Private Functions === */
241
242async fn guarded<T, E: std::error::Error + Send + Sync + 'static>(
243    phase: &'static str, future: impl Future<Output = Result<T, E>>,
244) -> Result<T, Cause> {
245    match AssertUnwindSafe(future).catch_unwind().await {
246        Ok(Ok(value)) => Ok(value),
247        Ok(Err(source)) => Err(Box::new(LifecycleFailure::Callback { phase, source: Box::new(source) })),
248        Err(_) => Err(Box::new(LifecycleFailure::Panicked { phase })),
249    }
250}
251
252/* === Tests === */