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 {
74            resources, lifecycle, stopping, report, ..
75        } = start;
76        let children = Arc::new(ChildSettlement {
77            pending: Mutex::new(Vec::new()),
78            lifecycle,
79            outcome: Mutex::new(None),
80            backend_returned: AtomicBool::new(false),
81            backend_cleanup: AtomicBool::new(false),
82        });
83        let completion = Arc::clone(&children);
84        let task = Task::with_cancellation(stopping, move |stopping| async move {
85            let admitted = completion.lifecycle.initialize_attempt(0);
86            report.publish();
87            let activation = ObserverActivation {
88                report: report.clone(),
89                stopping: stopping.subscribe(),
90                #[cfg(feature = "plugin-sidecar")]
91                children: Arc::clone(&completion),
92            };
93            // Calling backend code happens inside the owned attempt,
94            // including constructing its future, so panics are joined.
95            let outcome = supervision::joined(async move {
96                if !admitted {
97                    return Ok(());
98                }
99                let resources = resources.downcast::<B::Resources>().map_err(|_| Error::InvalidInput {
100                    field: "observer scope",
101                    reason: "resource type differs from the registered backend",
102                })?;
103                self.0.run(resources, activation).await
104            })
105            .await;
106            let outcome = match outcome {
107                Ok(outcome) => {
108                    completion.backend_returned.store(true, Ordering::Release);
109                    outcome.map_err(Arc::new)
110                }
111                Err(source) => Err(Arc::new(Error::TaskFailed {
112                    domain: "observer backend",
113                    source: Arc::new(source),
114                })),
115            };
116            *completion.outcome.lock().expect("observer outcome is healthy") = Some(outcome.clone());
117            if !completion.backend_returned.load(Ordering::Acquire) {
118                report.complete(&outcome);
119            }
120            let outcome = completion.finish(false).await;
121            report.complete(&outcome);
122            outcome
123        });
124        (task, children)
125    }
126}
127
128impl Settlement for ChildSettlement {
129    fn lifecycle(&self) -> &Arc<Instance> {
130        &self.lifecycle
131    }
132
133    fn retry(self: Arc<Self>, report: Report) -> Option<Completion> {
134        let retry = self.outcome.lock().expect("observer outcome is healthy").is_some() && !self.lifecycle.is_settled();
135        retry.then(|| {
136            Box::pin(async move {
137                let outcome = self.finish(true).await;
138                report.complete(&outcome);
139                outcome
140            }) as Completion
141        })
142    }
143}
144
145impl ChildSettlement {
146    // Retain one physical child of this activation until the activation
147    // settles; nothing new is admitted once the backend has returned.
148    #[cfg(feature = "plugin-sidecar")]
149    pub(crate) fn submit(
150        &self, retirement: bool, submit: impl FnOnce() -> Result<crate::operations::EncodedInvocation>,
151    ) -> Result<crate::operations::EncodedInvocation> {
152        let mut pending = self.pending.lock().expect("observer children are healthy");
153        pending.retain(|control| !control.is_settled());
154        if pending.len() >= 64 || (!retirement && self.outcome.lock().expect("observer outcome is healthy").is_some()) {
155            return Err(Error::ResourceExhausted { resource: "observer physical children" });
156        }
157        let invocation = submit()?;
158        pending.push(Arc::clone(&invocation.control));
159        Ok(invocation)
160    }
161
162    // Stop settling a child whose failed cleanup the backend retains itself.
163    #[cfg(feature = "plugin-sidecar")]
164    pub(crate) fn forget(&self, control: &Arc<Instance>) {
165        self.pending.lock().expect("observer children are healthy").retain(|pending| !Arc::ptr_eq(pending, control));
166    }
167
168    async fn finish(&self, recover: bool) -> Outcome {
169        let outcome = self
170            .outcome
171            .lock()
172            .expect("observer outcome is healthy")
173            .as_ref()
174            .expect("backend outcome recorded before child settlement")
175            .clone();
176        if self.lifecycle.status().outcome().is_none() {
177            let terminal = match &outcome {
178                Ok(()) if self.lifecycle.status().cancellation_requested() => supervision::instance::Outcome::Cancelled,
179                Ok(()) => supervision::instance::Outcome::Succeeded,
180                Err(_) => supervision::instance::Outcome::Failed,
181            };
182            self.lifecycle.complete(terminal);
183        }
184        if !self.backend_cleanup.load(Ordering::Acquire) {
185            self.lifecycle.transition(OperationPhase::Finalizing);
186        }
187        let pending = self.pending.lock().expect("observer children are healthy").clone();
188        if !self.backend_returned.load(Ordering::Acquire) {
189            for control in &pending {
190                control.cancel();
191            }
192        }
193        let joining = pending
194            .into_iter()
195            .map(|control| async move { control.settle().await.map_err(|source| Arc::new(Error::OperationSettlement { source })) });
196        if let Err(failure) = supervision::settle_all(joining).await {
197            self.lifecycle.interrupted(failure.clone());
198            return Err(failure);
199        }
200        self.pending.lock().expect("observer children are healthy").clear();
201        if self.backend_returned.load(Ordering::Acquire) {
202            if self.backend_cleanup.load(Ordering::Acquire) && !self.lifecycle.is_settled() {
203                if !recover {
204                    return Err(self.lifecycle.status().cleanup_failure().cloned().expect("retained backend failure"));
205                }
206                self.lifecycle.settle().await?;
207            } else {
208                self.lifecycle.finished();
209            }
210        } else if let Err(failure) = &outcome {
211            self.lifecycle.interrupted(failure.clone());
212        }
213        outcome
214    }
215}
216
217impl ObserverActivation {
218    pub fn key(&self) -> &resident::Key {
219        self.report.key()
220    }
221
222    pub fn id(&self) -> OperationInstanceId {
223        self.report.id()
224    }
225
226    // The activation initialized and observes.
227    pub fn ready(&self) {
228        self.report.transition(OperationPhase::Running);
229    }
230
231    // A handle that reports readiness from inside the observing future.
232    pub fn readiness(&self) -> impl Fn() + Send + Sync + 'static {
233        let report = self.report.clone();
234        move || report.transition(OperationPhase::Running)
235    }
236
237    // The activation stops: no lease joins it again.
238    pub fn begin_stopping(&self) {
239        self.report.quiesce();
240        self.report.transition(OperationPhase::Stopping);
241    }
242
243    #[cfg(feature = "plugin-sidecar")]
244    pub(crate) fn cleaning_up(&self) {
245        self.report.transition(OperationPhase::Finalizing);
246    }
247
248    pub async fn stopping(&mut self) {
249        while !*self.stopping.borrow_and_update() {
250            if self.stopping.changed().await.is_err() {
251                break;
252            }
253        }
254    }
255
256    // Keep a failed physical cleanup owned by this activation for a later retry.
257    #[cfg(feature = "plugin-sidecar")]
258    pub(crate) fn retain<R: supervision::cleanup::Resource>(&self, resource: R, failure: Arc<Error>) {
259        self.children.backend_cleanup.store(true, Ordering::Release);
260        self.children.lifecycle.retain(resource, failure);
261        self.report.publish();
262    }
263
264    #[cfg(feature = "plugin-sidecar")]
265    pub(crate) fn depend_on(&self, provider: &Arc<Instance>) -> Result<()> {
266        self.children.lifecycle.depend_on(provider, false)
267    }
268
269    #[cfg(feature = "plugin-sidecar")]
270    pub(crate) fn child_settlement(&self) -> Arc<ChildSettlement> {
271        Arc::clone(&self.children)
272    }
273}
274
275/* === Public Functions === */
276
277/* === Private Functions === */
278
279/* === Tests === */