Skip to main content

harmos_runtime/observers/
driver.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Runs one observer activation as a resident instance: the backend runs inside
11// the instance's owned attempt, and the physical children a remote backend
12// submits settle before the instance stops.
13
14/* === Modules === */
15
16/* === Re-Exports === */
17
18/* === Imports === */
19
20// Standard Library Imports
21use std::any::TypeId;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::{Arc, Mutex};
24// Third Party Imports
25use tokio::sync::watch;
26// Local Imports
27use super::ObserverBackend;
28use crate::Error;
29#[cfg(feature = "plugin-sidecar")]
30use crate::Result;
31use crate::operations::resident::{self, Completion, Outcome, Report, Runner, Settlement, Start};
32use crate::operations::{OperationInstanceId, OperationPhase};
33use crate::supervision::{self, Task, instance::Instance};
34
35/* === Constants === */
36
37/* === Macros === */
38
39/* === Type Aliases === */
40
41/* === Structs === */
42
43pub(crate) struct Backend<B>(pub(crate) B);
44
45pub(crate) struct ChildSettlement {
46    pending: Mutex<Vec<Arc<Instance>>>,
47    lifecycle: Arc<Instance>,
48    outcome: Mutex<Option<Outcome>>,
49    backend_returned: AtomicBool,
50    backend_cleanup: AtomicBool,
51}
52
53pub struct ObserverActivation {
54    report: Report,
55    stopping: watch::Receiver<bool>,
56
57    #[cfg(feature = "plugin-sidecar")]
58    children: Arc<ChildSettlement>,
59}
60
61/* === Enums === */
62
63/* === Traits === */
64
65/* === Implementations === */
66
67impl<B: ObserverBackend> Runner for Backend<B> {
68    fn resources(&self) -> TypeId {
69        TypeId::of::<B::Resources>()
70    }
71
72    fn spawn(self: Arc<Self>, start: Start) -> (Task<Outcome>, Arc<dyn Settlement>) {
73        let Start { resources, lifecycle, stopping, report, .. } = start;
74        let children = Arc::new(ChildSettlement {
75            pending: Mutex::new(Vec::new()),
76            lifecycle,
77            outcome: Mutex::new(None),
78            backend_returned: AtomicBool::new(false),
79            backend_cleanup: AtomicBool::new(false),
80        });
81        let completion = Arc::clone(&children);
82        let task = Task::with_cancellation(stopping, move |stopping| async move {
83            let admitted = completion.lifecycle.initialize_attempt(0);
84            report.publish();
85            let activation = ObserverActivation {
86                report: report.clone(),
87                stopping: stopping.subscribe(),
88                #[cfg(feature = "plugin-sidecar")]
89                children: Arc::clone(&completion),
90            };
91            // Calling backend code happens inside the owned attempt,
92            // including constructing its future, so panics are joined.
93            let outcome = supervision::joined(async move {
94                if !admitted {
95                    return Ok(());
96                }
97                let resources = resources.downcast::<B::Resources>().map_err(|_| Error::InvalidInput {
98                    field: "observer scope",
99                    reason: "resource type differs from the registered backend",
100                })?;
101                self.0.run(resources, activation).await
102            })
103            .await;
104            let outcome = match outcome {
105                Ok(outcome) => {
106                    completion.backend_returned.store(true, Ordering::Release);
107                    outcome.map_err(Arc::new)
108                }
109                Err(source) => Err(Arc::new(Error::TaskFailed { domain: "observer backend", source: Arc::new(source) })),
110            };
111            *completion.outcome.lock().expect("observer outcome is healthy") = Some(outcome.clone());
112            if !completion.backend_returned.load(Ordering::Acquire) {
113                report.complete(&outcome);
114            }
115            let outcome = completion.finish(false).await;
116            report.complete(&outcome);
117            outcome
118        });
119        (task, children)
120    }
121}
122
123impl Settlement for ChildSettlement {
124    fn lifecycle(&self) -> &Arc<Instance> {
125        &self.lifecycle
126    }
127
128    fn retry(self: Arc<Self>, report: Report) -> Option<Completion> {
129        let retry = self.outcome.lock().expect("observer outcome is healthy").is_some() && !self.lifecycle.is_settled();
130        retry.then(|| {
131            Box::pin(async move {
132                let outcome = self.finish(true).await;
133                report.complete(&outcome);
134                outcome
135            }) as Completion
136        })
137    }
138}
139
140impl ChildSettlement {
141    // Retain one physical child of this activation until the activation
142    // settles; nothing new is admitted once the backend has returned.
143    #[cfg(feature = "plugin-sidecar")]
144    pub(crate) fn submit(
145        &self, retirement: bool, submit: impl FnOnce() -> Result<crate::operations::EncodedInvocation>,
146    ) -> Result<crate::operations::EncodedInvocation> {
147        let mut pending = self.pending.lock().expect("observer children are healthy");
148        pending.retain(|control| !control.is_settled());
149        if pending.len() >= 64 || (!retirement && self.outcome.lock().expect("observer outcome is healthy").is_some()) {
150            return Err(Error::ResourceExhausted { resource: "observer physical children" });
151        }
152        let invocation = submit()?;
153        pending.push(Arc::clone(&invocation.control));
154        Ok(invocation)
155    }
156
157    // Stop settling a child whose failed cleanup the backend retains itself.
158    #[cfg(feature = "plugin-sidecar")]
159    pub(crate) fn forget(&self, control: &Arc<Instance>) {
160        self.pending.lock().expect("observer children are healthy").retain(|pending| !Arc::ptr_eq(pending, control));
161    }
162
163    async fn finish(&self, recover: bool) -> Outcome {
164        let outcome = self
165            .outcome
166            .lock()
167            .expect("observer outcome is healthy")
168            .as_ref()
169            .expect("backend outcome recorded before child settlement")
170            .clone();
171        if self.lifecycle.status().outcome().is_none() {
172            let terminal = match &outcome {
173                Ok(()) if self.lifecycle.status().cancellation_requested() => supervision::instance::Outcome::Cancelled,
174                Ok(()) => supervision::instance::Outcome::Succeeded,
175                Err(_) => supervision::instance::Outcome::Failed,
176            };
177            self.lifecycle.complete(terminal);
178        }
179        if !self.backend_cleanup.load(Ordering::Acquire) {
180            self.lifecycle.transition(OperationPhase::Finalizing);
181        }
182        let pending = self.pending.lock().expect("observer children are healthy").clone();
183        if !self.backend_returned.load(Ordering::Acquire) {
184            for control in &pending {
185                control.cancel();
186            }
187        }
188        let joining = pending
189            .into_iter()
190            .map(|control| async move { control.settle().await.map_err(|source| Arc::new(Error::OperationSettlement { source })) });
191        if let Err(failure) = supervision::settle_all(joining).await {
192            self.lifecycle.interrupted(failure.clone());
193            return Err(failure);
194        }
195        self.pending.lock().expect("observer children are healthy").clear();
196        if self.backend_returned.load(Ordering::Acquire) {
197            if self.backend_cleanup.load(Ordering::Acquire) && !self.lifecycle.is_settled() {
198                if !recover {
199                    return Err(self.lifecycle.status().cleanup_failure().cloned().expect("retained backend failure"));
200                }
201                self.lifecycle.settle().await?;
202            } else {
203                self.lifecycle.finished();
204            }
205        } else if let Err(failure) = &outcome {
206            self.lifecycle.interrupted(failure.clone());
207        }
208        outcome
209    }
210}
211
212impl ObserverActivation {
213    pub fn key(&self) -> &resident::Key {
214        self.report.key()
215    }
216
217    pub fn id(&self) -> OperationInstanceId {
218        self.report.id()
219    }
220
221    // The activation initialized and observes.
222    pub fn ready(&self) {
223        self.report.transition(OperationPhase::Running);
224    }
225
226    // A handle that reports readiness from inside the observing future.
227    pub fn readiness(&self) -> impl Fn() + Send + Sync + 'static {
228        let report = self.report.clone();
229        move || report.transition(OperationPhase::Running)
230    }
231
232    // The activation stops: no lease joins it again.
233    pub fn begin_stopping(&self) {
234        self.report.quiesce();
235        self.report.transition(OperationPhase::Stopping);
236    }
237
238    #[cfg(feature = "plugin-sidecar")]
239    pub(crate) fn cleaning_up(&self) {
240        self.report.transition(OperationPhase::Finalizing);
241    }
242
243    pub async fn stopping(&mut self) {
244        while !*self.stopping.borrow_and_update() {
245            if self.stopping.changed().await.is_err() {
246                break;
247            }
248        }
249    }
250
251    // Keep a failed physical cleanup owned by this activation for a later retry.
252    #[cfg(feature = "plugin-sidecar")]
253    pub(crate) fn retain<R: supervision::cleanup::Resource>(&self, resource: R, failure: Arc<Error>) {
254        self.children.backend_cleanup.store(true, Ordering::Release);
255        self.children.lifecycle.retain(resource, failure);
256        self.report.publish();
257    }
258
259    #[cfg(feature = "plugin-sidecar")]
260    pub(crate) fn depend_on(&self, provider: &Arc<Instance>) -> Result<()> {
261        self.children.lifecycle.depend_on(provider, false)
262    }
263
264    #[cfg(feature = "plugin-sidecar")]
265    pub(crate) fn child_settlement(&self) -> Arc<ChildSettlement> {
266        Arc::clone(&self.children)
267    }
268}
269
270/* === Public Functions === */
271
272/* === Private Functions === */
273
274/* === Tests === */