harmos_runtime/runtime/
boot.rs1use std::future::Future;
18use std::pin::Pin;
19use std::sync::{Arc, Mutex, Weak};
20use std::task::{Context, Poll};
21use std::time::Duration;
22use tokio::sync::watch;
24use super::Runtime;
26use crate::storage::StorageManager;
27use crate::{Application, Result};
28
29type StartupFuture<A> = Pin<Box<dyn Future<Output = std::result::Result<Runtime<A>, super::Incompatible>> + Send>>;
36type CleanupFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
37type ReconcileFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
38type InitializeFuture = Pin<Box<dyn Future<Output = std::result::Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send>>;
39type Initialize<A> = Box<dyn FnOnce(super::Handle<A>) -> InitializeFuture + Send>;
40pub(super) type Reconciliation<A> =
41 Box<dyn FnOnce(super::StateManager<A>, StorageManager<crate::storage::ErasedCatalog>) -> ReconcileFuture + Send>;
42
43pub(super) struct Initializer<A: Application> {
46 pub(super) timeout: Duration,
47 pub(super) initialize: Initialize<A>,
48}
49
50#[derive(Clone)]
51pub(super) struct Cleanup {
52 close: Arc<dyn Fn() -> CleanupFuture + Send + Sync>,
53 receipt: Weak<CleanupState>,
54}
55
56pub struct Startup<A: Application> {
57 future: Option<StartupFuture<A>>,
58 cleanup: BootstrapCleanup,
59}
60
61#[derive(Clone)]
62pub struct BootstrapCleanup {
63 shared: Arc<CleanupState>,
64}
65
66struct CleanupState {
67 retained: Mutex<Retained>,
68 status: watch::Sender<BootstrapStatus>,
69}
70
71struct Retained {
72 owner: Option<Cleanup>,
73 failure: Option<Arc<crate::Error>>,
74}
75
76pub(super) struct Boot<A: Application> {
77 pub(super) identity: Arc<()>,
78 pub(super) storage: Option<StorageManager<crate::storage::ErasedCatalog>>,
79 pub(super) runtime: Option<Runtime<A>>,
80 pub(super) writer: Option<tokio::task::JoinHandle<()>>,
81 pub(super) state: Option<super::StateManager<A>>,
82 armed: bool,
83 cleanup: BootstrapCleanup,
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum BootstrapStatus {
90 Starting,
91 Cleaning,
92 Failed,
93 Settled,
94 HandedOff,
95}
96
97impl<A: Application> Startup<A> {
102 pub(super) fn new(builder: super::Builder<A>, storage: Option<StorageManager<crate::storage::ErasedCatalog>>) -> Self {
103 let cleanup = BootstrapCleanup::new();
104 let receipt = cleanup.clone();
105 let future = Box::pin(async move {
106 let outcome = builder.boot(storage.as_ref(), receipt.clone()).await;
107 receipt.settle_unowned();
108 outcome
109 });
110 Self { future: Some(future), cleanup }
111 }
112
113 pub fn cleanup(&self) -> BootstrapCleanup {
114 self.cleanup.clone()
115 }
116}
117
118impl<A: Application> Future for Startup<A> {
119 type Output = std::result::Result<Runtime<A>, super::Incompatible>;
120 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
121 let future = self.future.as_mut().expect("startup is polled before completion");
122 let outcome = future.as_mut().poll(context);
123 if outcome.is_ready() {
124 self.future.take();
125 }
126 outcome
127 }
128}
129
130impl<A: Application> Drop for Startup<A> {
131 fn drop(&mut self) {
132 self.future.take();
134 self.cleanup.settle_unowned();
135 }
136}
137
138impl BootstrapCleanup {
139 fn new() -> Self {
140 let (status, _) = watch::channel(BootstrapStatus::Starting);
141 Self { shared: Arc::new(CleanupState { retained: Mutex::new(Retained { owner: None, failure: None }), status }) }
142 }
143
144 pub fn status(&self) -> BootstrapStatus {
145 *self.shared.status.borrow()
146 }
147
148 pub fn failure(&self) -> Option<Arc<crate::Error>> {
149 self.shared.retained.lock().expect("bootstrap cleanup lock").failure.clone()
150 }
151
152 pub async fn wait(&self) -> Result<()> {
153 let mut status = self.shared.status.subscribe();
154 loop {
155 let current = *status.borrow_and_update();
156 match current {
157 BootstrapStatus::Settled | BootstrapStatus::HandedOff => return Ok(()),
158 BootstrapStatus::Failed => {
159 let source = {
160 let retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
161 if self.status() != BootstrapStatus::Failed {
162 continue;
163 }
164 retained.failure.clone().expect("failed cleanup retains its cause")
165 };
166 return Err(crate::Error::TaskFailed { domain: "bootstrap cleanup", source });
167 }
168 BootstrapStatus::Starting | BootstrapStatus::Cleaning => {}
169 }
170 status.changed().await.expect("receipt retains its status owner");
171 }
172 }
173
174 pub async fn retry(&self) -> Result<()> {
175 let owner = {
176 let mut retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
177 if self.status() == BootstrapStatus::Failed {
178 let owner = retained.owner.clone().expect("failed cleanup owns resources");
179 retained.failure = None;
180 self.shared.status.send_replace(BootstrapStatus::Cleaning);
181 Some(owner)
182 } else {
183 None
184 }
185 };
186 if let Some(owner) = owner {
187 self.run(owner);
188 }
189 self.wait().await
190 }
191
192 fn run(&self, owner: Cleanup) {
193 tokio::spawn(async move {
194 let _ = owner.close().await;
195 });
196 }
197
198 pub(super) fn failed(&self, owner: Option<Cleanup>, failure: Arc<crate::Error>) {
199 let mut retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
200 retained.owner = owner;
201 retained.failure = Some(failure);
202 self.shared.status.send_replace(BootstrapStatus::Failed);
203 }
204
205 fn cancelled(&self, owner: Cleanup) {
206 {
207 let mut retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
208 retained.owner = Some(owner.clone());
209 self.shared.status.send_replace(BootstrapStatus::Cleaning);
210 }
211 self.run(owner);
212 }
213
214 fn settle_unowned(&self) {
215 let retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
216 if retained.owner.is_none() && self.status() == BootstrapStatus::Starting {
217 self.shared.status.send_replace(BootstrapStatus::Settled);
218 }
219 }
220}
221
222impl<A: Application> Initializer<A> {
223 pub(super) fn new<F, Fut, E>(timeout: Duration, initialize: F) -> Self
224 where
225 F: FnOnce(super::Handle<A>) -> Fut + Send + 'static,
226 Fut: Future<Output = std::result::Result<(), E>> + Send + 'static,
227 E: std::error::Error + Send + Sync + 'static,
228 {
229 Self {
230 timeout,
231 initialize: Box::new(move |handle| Box::pin(async move { initialize(handle).await.map_err(|source| Box::new(source) as _) })),
232 }
233 }
234}
235
236impl Cleanup {
237 pub(super) async fn close(&self) -> Result<()> {
238 let outcome = (self.close)().await;
239 let Some(receipt) = self.receipt.upgrade() else {
240 return outcome;
241 };
242 let mut retained = receipt.retained.lock().expect("bootstrap cleanup lock");
243 match outcome {
244 Ok(()) => {
245 retained.owner = None;
246 retained.failure = None;
247 receipt.status.send_replace(BootstrapStatus::Settled);
248 Ok(())
249 }
250 Err(error) => {
251 let source = Arc::new(error);
252 retained.failure = Some(Arc::clone(&source));
253 receipt.status.send_replace(BootstrapStatus::Failed);
254 Err(crate::Error::TaskFailed { domain: "bootstrap cleanup", source })
255 }
256 }
257 }
258}
259
260impl std::fmt::Debug for Cleanup {
261 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 formatter.debug_struct("Cleanup").finish_non_exhaustive()
263 }
264}
265
266impl<A: Application> Boot<A> {
267 pub(super) fn new(storage: Option<&StorageManager<crate::storage::ErasedCatalog>>, cleanup: BootstrapCleanup) -> Result<Self> {
268 let identity = Arc::new(());
269 if let Some(storage) = storage {
270 storage.claim_owner(identity.clone())?;
273 }
274
275 Ok(Self { identity, storage: storage.cloned(), runtime: None, writer: None, state: None, armed: true, cleanup })
276 }
277
278 pub(super) async fn acquire(&self) -> Result<()> {
279 if let Some(storage) = &self.storage {
280 storage.acquire().await?;
281 }
282 Ok(())
283 }
284
285 pub(super) async fn close(&mut self) -> Result<()> {
286 if self.runtime.is_none() {
287 if let Some(state) = self.state.take() {
288 state.seal();
289 }
290 if let Some(writer) = self.writer.as_mut() {
291 let _ = writer.await;
292 }
293 self.writer.take();
294 }
295 let result = if let Some(runtime) = &self.runtime {
296 runtime.stop().await.map(|_| ())
297 } else if let Some(storage) = &self.storage {
298 storage.close().await
299 } else {
300 Ok(())
301 };
302 self.armed = false;
305 if result.is_ok() {
306 self.runtime.take();
307 }
308 result
309 }
310
311 pub(super) fn retain_cleanup(&mut self) -> Option<Cleanup> {
312 if let Some(runtime) = self.runtime.take() {
313 let runtime = Arc::new(runtime);
314 return Some(Cleanup {
315 receipt: Arc::downgrade(&self.cleanup.shared),
316 close: Arc::new(move || {
317 let runtime = Arc::clone(&runtime);
318 Box::pin(async move { runtime.stop().await.map(|_| ()) })
319 }),
320 });
321 }
322 self.storage.take().map(|storage| Cleanup {
323 receipt: Arc::downgrade(&self.cleanup.shared),
324 close: Arc::new(move || {
325 let storage = storage.clone();
326 Box::pin(async move { storage.close().await })
327 }),
328 })
329 }
330
331 pub(super) fn finish(mut self) -> Runtime<A> {
332 self.armed = false;
333 self.cleanup.shared.status.send_replace(BootstrapStatus::HandedOff);
334 self.runtime.take().expect("successful boot owns a runtime")
335 }
336}
337
338impl<A: Application> Drop for Boot<A> {
339 fn drop(&mut self) {
340 if !self.armed {
341 return;
342 }
343 let state = self.state.take();
344 let writer = self.writer.take();
345 let runtime = self.runtime.take();
346 let storage = self.storage.take();
347 if runtime.is_none() && storage.is_none() && writer.is_none() {
348 return;
349 }
350 let writer = Arc::new(tokio::sync::Mutex::new(writer));
352 let runtime = runtime.map(Arc::new);
353 let owner = Cleanup {
354 receipt: Arc::downgrade(&self.cleanup.shared),
355 close: Arc::new(move || {
356 let state = state.clone();
357 let writer = Arc::clone(&writer);
358 let runtime = runtime.clone();
359 let storage = storage.clone();
360 Box::pin(async move {
361 if runtime.is_none() {
362 if let Some(state) = state {
363 state.seal();
364 }
365 let mut writer = writer.lock().await;
366 if let Some(task) = writer.as_mut() {
367 let _ = task.await;
368 }
369 writer.take();
370 }
371 if let Some(runtime) = runtime {
372 runtime.stop().await.map(|_| ())
373 } else if let Some(storage) = storage {
374 storage.close().await
375 } else {
376 Ok(())
377 }
378 })
379 }),
380 };
381 self.cleanup.cancelled(owner);
382 }
383}
384
385