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 {
142 shared: Arc::new(CleanupState {
143 retained: Mutex::new(Retained { owner: None, failure: None }),
144 status,
145 }),
146 }
147 }
148
149 pub fn status(&self) -> BootstrapStatus {
150 *self.shared.status.borrow()
151 }
152
153 pub fn failure(&self) -> Option<Arc<crate::Error>> {
154 self.shared.retained.lock().expect("bootstrap cleanup lock").failure.clone()
155 }
156
157 pub async fn wait(&self) -> Result<()> {
158 let mut status = self.shared.status.subscribe();
159 loop {
160 let current = *status.borrow_and_update();
161 match current {
162 BootstrapStatus::Settled | BootstrapStatus::HandedOff => return Ok(()),
163 BootstrapStatus::Failed => {
164 let source = {
165 let retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
166 if self.status() != BootstrapStatus::Failed {
167 continue;
168 }
169 retained.failure.clone().expect("failed cleanup retains its cause")
170 };
171 return Err(crate::Error::TaskFailed { domain: "bootstrap cleanup", source });
172 }
173 BootstrapStatus::Starting | BootstrapStatus::Cleaning => {}
174 }
175 status.changed().await.expect("receipt retains its status owner");
176 }
177 }
178
179 pub async fn retry(&self) -> Result<()> {
180 let owner = {
181 let mut retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
182 if self.status() == BootstrapStatus::Failed {
183 let owner = retained.owner.clone().expect("failed cleanup owns resources");
184 retained.failure = None;
185 self.shared.status.send_replace(BootstrapStatus::Cleaning);
186 Some(owner)
187 } else {
188 None
189 }
190 };
191 if let Some(owner) = owner {
192 self.run(owner);
193 }
194 self.wait().await
195 }
196
197 fn run(&self, owner: Cleanup) {
198 tokio::spawn(async move {
199 let _ = owner.close().await;
200 });
201 }
202
203 pub(super) fn failed(&self, owner: Option<Cleanup>, failure: Arc<crate::Error>) {
204 let mut retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
205 retained.owner = owner;
206 retained.failure = Some(failure);
207 self.shared.status.send_replace(BootstrapStatus::Failed);
208 }
209
210 fn cancelled(&self, owner: Cleanup) {
211 {
212 let mut retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
213 retained.owner = Some(owner.clone());
214 self.shared.status.send_replace(BootstrapStatus::Cleaning);
215 }
216 self.run(owner);
217 }
218
219 fn settle_unowned(&self) {
220 let retained = self.shared.retained.lock().expect("bootstrap cleanup lock");
221 if retained.owner.is_none() && self.status() == BootstrapStatus::Starting {
222 self.shared.status.send_replace(BootstrapStatus::Settled);
223 }
224 }
225}
226
227impl<A: Application> Initializer<A> {
228 pub(super) fn new<F, Fut, E>(timeout: Duration, initialize: F) -> Self
229 where
230 F: FnOnce(super::Handle<A>) -> Fut + Send + 'static,
231 Fut: Future<Output = std::result::Result<(), E>> + Send + 'static,
232 E: std::error::Error + Send + Sync + 'static,
233 {
234 Self {
235 timeout,
236 initialize: Box::new(move |handle| Box::pin(async move { initialize(handle).await.map_err(|source| Box::new(source) as _) })),
237 }
238 }
239}
240
241impl Cleanup {
242 pub(super) async fn close(&self) -> Result<()> {
243 let outcome = (self.close)().await;
244 let Some(receipt) = self.receipt.upgrade() else {
245 return outcome;
246 };
247 let mut retained = receipt.retained.lock().expect("bootstrap cleanup lock");
248 match outcome {
249 Ok(()) => {
250 retained.owner = None;
251 retained.failure = None;
252 receipt.status.send_replace(BootstrapStatus::Settled);
253 Ok(())
254 }
255 Err(error) => {
256 let source = Arc::new(error);
257 retained.failure = Some(Arc::clone(&source));
258 receipt.status.send_replace(BootstrapStatus::Failed);
259 Err(crate::Error::TaskFailed { domain: "bootstrap cleanup", source })
260 }
261 }
262 }
263}
264
265impl std::fmt::Debug for Cleanup {
266 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 formatter.debug_struct("Cleanup").finish_non_exhaustive()
268 }
269}
270
271impl<A: Application> Boot<A> {
272 pub(super) fn new(storage: Option<&StorageManager<crate::storage::ErasedCatalog>>, cleanup: BootstrapCleanup) -> Result<Self> {
273 let identity = Arc::new(());
274 if let Some(storage) = storage {
275 storage.claim_owner(identity.clone())?;
278 }
279
280 Ok(Self {
281 identity,
282 storage: storage.cloned(),
283 runtime: None,
284 writer: None,
285 state: None,
286 armed: true,
287 cleanup,
288 })
289 }
290
291 pub(super) async fn acquire(&self) -> Result<()> {
292 if let Some(storage) = &self.storage {
293 storage.acquire().await?;
294 }
295 Ok(())
296 }
297
298 pub(super) async fn close(&mut self) -> Result<()> {
299 if self.runtime.is_none() {
300 if let Some(state) = self.state.take() {
301 state.seal();
302 }
303 if let Some(writer) = self.writer.as_mut() {
304 let _ = writer.await;
305 }
306 self.writer.take();
307 }
308 let result = if let Some(runtime) = &self.runtime {
309 runtime.stop().await.map(|_| ())
310 } else if let Some(storage) = &self.storage {
311 storage.close().await
312 } else {
313 Ok(())
314 };
315 self.armed = false;
318 if result.is_ok() {
319 self.runtime.take();
320 }
321 result
322 }
323
324 pub(super) fn retain_cleanup(&mut self) -> Option<Cleanup> {
325 if let Some(runtime) = self.runtime.take() {
326 let runtime = Arc::new(runtime);
327 return Some(Cleanup {
328 receipt: Arc::downgrade(&self.cleanup.shared),
329 close: Arc::new(move || {
330 let runtime = Arc::clone(&runtime);
331 Box::pin(async move { runtime.stop().await.map(|_| ()) })
332 }),
333 });
334 }
335 self.storage.take().map(|storage| Cleanup {
336 receipt: Arc::downgrade(&self.cleanup.shared),
337 close: Arc::new(move || {
338 let storage = storage.clone();
339 Box::pin(async move { storage.close().await })
340 }),
341 })
342 }
343
344 pub(super) fn finish(mut self) -> Runtime<A> {
345 self.armed = false;
346 self.cleanup.shared.status.send_replace(BootstrapStatus::HandedOff);
347 self.runtime.take().expect("successful boot owns a runtime")
348 }
349}
350
351impl<A: Application> Drop for Boot<A> {
352 fn drop(&mut self) {
353 if !self.armed {
354 return;
355 }
356 let state = self.state.take();
357 let writer = self.writer.take();
358 let runtime = self.runtime.take();
359 let storage = self.storage.take();
360 if runtime.is_none() && storage.is_none() && writer.is_none() {
361 return;
362 }
363 let writer = Arc::new(tokio::sync::Mutex::new(writer));
365 let runtime = runtime.map(Arc::new);
366 let owner = Cleanup {
367 receipt: Arc::downgrade(&self.cleanup.shared),
368 close: Arc::new(move || {
369 let state = state.clone();
370 let writer = Arc::clone(&writer);
371 let runtime = runtime.clone();
372 let storage = storage.clone();
373 Box::pin(async move {
374 if runtime.is_none() {
375 if let Some(state) = state {
376 state.seal();
377 }
378 let mut writer = writer.lock().await;
379 if let Some(task) = writer.as_mut() {
380 let _ = task.await;
381 }
382 writer.take();
383 }
384 if let Some(runtime) = runtime {
385 runtime.stop().await.map(|_| ())
386 } else if let Some(storage) = storage {
387 storage.close().await
388 } else {
389 Ok(())
390 }
391 })
392 }),
393 };
394 self.cleanup.cancelled(owner);
395 }
396}
397
398