harmos_runtime/runtime/
launch.rs1use std::future::Future;
20use std::panic::AssertUnwindSafe;
21use std::sync::{Arc, Mutex};
22
23use futures_util::FutureExt;
25
26use super::{Builder, Runtime, StartupError};
28use crate::operations::{Lane, LaneSettings};
29use crate::supervision::cleanup::{Cleanup, Finalize, Resource};
30use crate::{Application, Error, Setup, Startup};
31
32type Cause = Box<dyn std::error::Error + Send + Sync>;
39
40pub(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#[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
76impl 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
238async 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