Skip to main content

harmos_sdk/plugin/
owner.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::future::Future;
18use std::num::NonZeroUsize;
19use std::pin::Pin;
20use std::sync::{Arc, Mutex, Weak};
21// Third Party Imports
22
23// Local Imports
24use super::{Attachment, Context as PluginContext, Error, Plugin};
25use crate::operations::{Contract, ExecutionContext, ServingError, WireTable};
26use harmos_domain::model::operations::lifecycle;
27
28/* === Constants === */
29
30/* === Macros === */
31
32/* === Type Aliases === */
33
34pub type Table = Arc<Mutex<WireTable>>;
35type Transition<P> = Pin<Box<dyn Future<Output = (P, Result<(), Error<P>>, bool)> + Send>>;
36
37/* === Structs === */
38
39pub struct Owner<P: Plugin> {
40    slot: Arc<Mutex<Slot<P>>>,
41    table: Weak<Mutex<WireTable>>,
42    contracts: Arc<[Contract]>,
43    observers: Arc<[crate::inventory::observers::Declaration]>,
44    retirement: Mutex<Retirement<P>>,
45}
46
47struct LifecycleInvocation<P: Plugin> {
48    owner: Arc<Owner<P>>,
49    state: Option<State<P>>,
50    action: lifecycle::Action,
51}
52
53struct Retirement<P: Plugin> {
54    state: Option<State<P>>,
55    active: bool,
56    finished: bool,
57}
58
59struct RetirementLoan<P: Plugin> {
60    owner: Arc<Owner<P>>,
61    state: Option<State<P>>,
62    finished: bool,
63}
64
65struct Slot<P: Plugin> {
66    plugin: Option<P>,
67    phase: Phase,
68    attachment: Option<Arc<dyn Attachment<P::Context>>>,
69    generation_context: Option<[u8; 32]>,
70}
71
72struct State<P: Plugin> {
73    slot: Arc<Mutex<Slot<P>>>,
74    table: Weak<Mutex<WireTable>>,
75    plugin: Option<P>,
76    work: Option<Transition<P>>,
77    result: Option<Result<(), Error<P>>>,
78    action: lifecycle::Action,
79    delivered: bool,
80    failure_reported: bool,
81    shutdown_attempted: bool,
82    context: Option<P::Context>,
83    assignments: Vec<u8>,
84    attachment: Option<Arc<dyn Attachment<P::Context>>>,
85    generation_context: Option<[u8; 32]>,
86}
87
88/* === Enums === */
89
90#[derive(Clone, Copy, PartialEq, Eq)]
91enum Phase {
92    Empty,
93    Initializing,
94    Initialized,
95    Stopping,
96    Closed,
97}
98
99/* === Traits === */
100
101/* === Implementations === */
102
103impl<P: Plugin> Owner<P> {
104    pub fn new(owner: &str, contracts: Vec<Contract>, capacity: NonZeroUsize) -> Result<(Arc<Self>, Table), Error<P>> {
105        Self::new_with_observers(owner, contracts, capacity, Vec::new())
106    }
107
108    pub fn new_with_observers(
109        assigned_owner: &str, contracts: Vec<Contract>, capacity: NonZeroUsize, observers: Vec<crate::inventory::observers::Declaration>,
110    ) -> Result<(Arc<Self>, Table), Error<P>> {
111        let definitions: Vec<_> = contracts.iter().map(|contract| contract.operation().clone()).collect();
112        crate::inventory::operations::validate(&definitions).map_err(|error| Error::Inventory(Arc::new(error)))?;
113        let mut serving = WireTable::new(assigned_owner).map_err(|error| Error::Serving(Arc::new(error)))?;
114        serving.capacity(capacity);
115        serving.provide(std::any::TypeId::of::<P::Context>());
116        serving.provide(std::any::TypeId::of::<crate::operations::Calls>());
117        let table = Arc::new(Mutex::new(serving));
118        let owner = Arc::new(Self {
119            slot: Arc::new(Mutex::new(Slot {
120                plugin: None,
121                phase: Phase::Empty,
122                attachment: None,
123                generation_context: None,
124            })),
125            table: Arc::downgrade(&table),
126            contracts: contracts.into(),
127            observers: observers.into(),
128            retirement: Mutex::new(Retirement {
129                state: None,
130                active: false,
131                finished: false,
132            }),
133        });
134        Ok((owner, table))
135    }
136
137    pub async fn quiesce(&self) -> Result<(), Error<P>> {
138        let attachment = self.slot.lock().map_err(|_| Error::Poisoned)?.attachment.clone();
139        if let Some(attachment) = attachment {
140            guarded(async { attachment.quiesce().await.map_err(|error| Error::Serving(Arc::new(error))) }).await?;
141        }
142        Ok(())
143    }
144
145    pub async fn retire(self: &Arc<Self>) -> Result<(), Error<P>> {
146        let table = self.table.upgrade().ok_or(Error::Phase)?;
147        {
148            let table = table.lock().map_err(|_| Error::Poisoned)?;
149            if !table.is_settled() {
150                return Err(Error::Phase);
151            }
152        }
153        let mut loan = {
154            let mut retirement = self.retirement.lock().map_err(|_| Error::Poisoned)?;
155            if retirement.finished {
156                return Ok(());
157            }
158            if retirement.active {
159                return Err(Error::Phase);
160            }
161            let state = match retirement.state.take() {
162                Some(state) => state,
163                None => self.prepare_shutdown()?,
164            };
165            retirement.active = true;
166            RetirementLoan {
167                owner: Arc::clone(self),
168                state: Some(state),
169                finished: false,
170            }
171        };
172        loan.state.as_mut().ok_or(Error::Phase)?.finalize().await?;
173        loan.finished = true;
174        Ok(())
175    }
176
177    fn state(&self, plugin: Option<P>, action: lifecycle::Action) -> State<P> {
178        State {
179            slot: Arc::clone(&self.slot),
180            table: self.table.clone(),
181            plugin,
182            work: None,
183            result: None,
184            action,
185            delivered: false,
186            failure_reported: false,
187            shutdown_attempted: false,
188            context: None,
189            assignments: Vec::new(),
190            attachment: None,
191            generation_context: None,
192        }
193    }
194}
195
196impl<P: Plugin> Drop for RetirementLoan<P> {
197    fn drop(&mut self) {
198        let mut retirement = self.owner.retirement.lock().unwrap_or_else(|error| error.into_inner());
199        retirement.active = false;
200        retirement.finished = self.finished;
201        if !self.finished {
202            retirement.state = self.state.take();
203        }
204    }
205}
206
207impl<P: Plugin> Owner<P> {
208    fn prepare_initialize(&self, config_json: &[u8], generation_context: &[u8], assignments: &[u8]) -> Result<State<P>, Error<P>> {
209        let generation_context = if generation_context.is_empty() {
210            None
211        } else {
212            let token: [u8; 32] = generation_context.try_into().map_err(|_| Error::GenerationContext)?;
213            if token == [0; 32] {
214                return Err(Error::GenerationContext);
215            }
216            Some(token)
217        };
218        let config = crate::decode_config(config_json).map_err(|error| Error::Configuration(Arc::new(error)))?;
219        let plugin = P::prepare(config).map_err(|error| Error::Plugin(Arc::new(error)))?;
220        let mut slot = self.slot.lock().map_err(|_| Error::Poisoned)?;
221        if slot.phase != Phase::Empty {
222            return Err(Error::Phase);
223        }
224        slot.phase = Phase::Initializing;
225        let mut state = self.state(Some(plugin), lifecycle::Action::Initialize);
226        state.generation_context = generation_context;
227        state.assignments = assignments.to_vec();
228        Ok(state)
229    }
230
231    async fn initialize(&self, state: &mut State<P>, context: &P::Context) -> Result<(), Error<P>> {
232        if state.work.is_none() && state.result.is_none() {
233            context.assign_operations(&state.assignments).map_err(|error| Error::Serving(Arc::new(error)))?;
234            let mut plugin = state.plugin.take().ok_or(Error::Phase)?;
235            let context = state.generation_context.map_or_else(|| context.clone(), |token| context.with_generation(token));
236            state.work = Some(Box::pin(async move {
237                let result = guarded(async { plugin.initialize(&context).await.map_err(|error| Error::Plugin(Arc::new(error))) }).await;
238                (plugin, result, false)
239            }));
240        }
241        state.finish().await?;
242        state.result.as_ref().ok_or(Error::Phase)?.clone()?;
243        if state.delivered {
244            return Ok(());
245        }
246        let inventory = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
247            let plugin = state.plugin.as_ref().ok_or(Error::Phase)?;
248            state.attachment = plugin.attachment();
249            if let Some(attachment) = &state.attachment {
250                let resources: Arc<dyn std::any::Any + Send + Sync> = plugin.resources();
251                if !Arc::ptr_eq(&resources, &attachment.resources()) {
252                    return Err(Error::Serving(Arc::new(ServingError::Catalog)));
253                }
254            }
255            let actual = state.attachment.as_ref().map_or_else(Vec::new, |attachment| attachment.observers());
256            if actual.as_slice() != self.observers.as_ref() {
257                return Err(Error::Serving(Arc::new(ServingError::Catalog)));
258            }
259            Ok(())
260        }))
261        .map_err(|_| Error::Panicked)
262        .and_then(|result| result);
263        if let Err(error) = inventory {
264            state.result = Some(Err(error.clone()));
265            return Err(error);
266        }
267        let attachment = state.attachment.clone();
268        let table = self.table.upgrade().ok_or(Error::Phase)?;
269        let binding = {
270            let mut table = table.lock().map_err(|_| Error::Poisoned)?;
271            let plugin = state.plugin.as_ref().ok_or(Error::Phase)?;
272            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
273                plugin
274                    .bind_operations(&mut table)
275                    .and_then(|()| table.validate_contracts(&self.contracts))
276                    .and_then(|()| table.validate_callees(&state.assignments))
277            }))
278            .map_err(|_| Error::Panicked)
279            .and_then(|result| result.map_err(|error| Error::Serving(Arc::new(error))))
280        };
281        if let Err(error) = binding {
282            state.result = Some(Err(error.clone()));
283            return Err(error);
284        }
285        let mut slot = self.slot.lock().map_err(|_| Error::Poisoned)?;
286        slot.plugin = state.plugin.take();
287        slot.attachment = attachment;
288        slot.generation_context = state.generation_context;
289        slot.phase = Phase::Initialized;
290        state.delivered = true;
291        Ok(())
292    }
293
294    fn prepare_shutdown(&self) -> Result<State<P>, Error<P>> {
295        let mut slot = self.slot.lock().map_err(|_| Error::Poisoned)?;
296        if matches!(slot.phase, Phase::Empty | Phase::Closed) {
297            let mut state = self.state(None, lifecycle::Action::Shutdown);
298            state.result = Some(Ok(()));
299            return Ok(state);
300        }
301        if slot.phase != Phase::Initialized {
302            return Err(Error::Phase);
303        }
304        let plugin = slot.plugin.take().ok_or(Error::Phase)?;
305        slot.phase = Phase::Stopping;
306        let mut state = self.state(Some(plugin), lifecycle::Action::Shutdown);
307        state.attachment = slot.attachment.take();
308        state.generation_context = slot.generation_context;
309        Ok(state)
310    }
311}
312
313impl<P: Plugin> crate::operations::lifecycle::PluginLifecycle for Owner<P> {
314    fn begin(self: Arc<Self>, action: lifecycle::Action) -> Box<dyn crate::operations::lifecycle::LifecycleInstance> {
315        Box::new(LifecycleInvocation { owner: self, state: None, action })
316    }
317}
318impl<P: Plugin> crate::operations::lifecycle::LifecycleInstance for LifecycleInvocation<P> {
319    fn run<'a>(
320        &'a mut self, context: ExecutionContext, config: &'a [u8], generation: &'a [u8], assignments: &'a [u8],
321    ) -> crate::operations::lifecycle::Transition<'a> {
322        Box::pin(async move {
323            let result: Result<(), Error<P>> = async {
324                match self.action {
325                    lifecycle::Action::Initialize => {
326                        if self.state.is_none() {
327                            self.state = Some(self.owner.prepare_initialize(config, generation, assignments)?);
328                        }
329                        let carrier = context.resource::<P::Context>().map_err(|error| Error::Resource(Arc::new(error)))?;
330                        self.owner.initialize(self.state.as_mut().ok_or(Error::Phase)?, &carrier).await
331                    }
332                    lifecycle::Action::Shutdown => {
333                        if self.state.is_none() {
334                            self.state = Some(self.owner.prepare_shutdown()?);
335                        }
336                        let state = self.state.as_mut().ok_or(Error::Phase)?;
337                        if state.work.is_none() && state.result.is_none() {
338                            let carrier = context.resource::<P::Context>().map_err(|error| Error::Resource(Arc::new(error)))?;
339                            state.context =
340                                Some(state.generation_context.map_or_else(|| (*carrier).clone(), |token| carrier.with_generation(token)));
341                            state.start_shutdown()?;
342                        }
343                        state.finish().await?;
344                        state.result.as_ref().ok_or(Error::Phase)?.clone()
345                    }
346                }
347            }
348            .await;
349            result.map_err(Into::into)
350        })
351    }
352    fn cleanup(&mut self) -> crate::operations::lifecycle::Transition<'_> {
353        Box::pin(async {
354            match self.state.as_mut() {
355                Some(state) => state.finalize().await.map_err(Into::into),
356                None => Ok(()),
357            }
358        })
359    }
360}
361
362impl<P: Plugin> State<P> {
363    async fn finish(&mut self) -> Result<(), Error<P>> {
364        if let Some(work) = &mut self.work {
365            let (plugin, result, shutdown_attempted) = work.await;
366            self.shutdown_attempted = shutdown_attempted;
367            self.plugin = Some(plugin);
368            self.result = Some(result);
369            self.work = None;
370        }
371        Ok(())
372    }
373
374    fn start_shutdown(&mut self) -> Result<(), Error<P>> {
375        let attachment = self.attachment.clone().or_else(|| self.plugin.as_ref().and_then(Plugin::attachment));
376        let mut plugin = self.plugin.take().ok_or(Error::Phase)?;
377        let table = self.table.clone();
378        let context = self.context.clone();
379        let mut shutdown_attempted = self.shutdown_attempted;
380        self.result = None;
381        self.work = Some(Box::pin(async move {
382            let result = guarded(async {
383                if let Some(attachment) = attachment {
384                    attachment.quiesce().await.map_err(|error| Error::Serving(Arc::new(error)))?;
385                }
386                let table = table.upgrade().ok_or(Error::Phase)?;
387                let settlement = {
388                    let mut table = table.lock().map_err(|_| Error::Poisoned)?;
389                    table.close_ordinary()
390                };
391                settlement.await.map_err(|error| Error::Serving(Arc::new(error)))?;
392                let retired = {
393                    let mut table = table.lock().map_err(|_| Error::Poisoned)?;
394                    table.release_ordinary().map_err(|error| Error::Serving(Arc::new(error)))?
395                };
396                drop(retired);
397                if !shutdown_attempted && let Some(context) = &context {
398                    // Domain Effects Are Never Replayed By Physical Cleanup Retries.
399                    shutdown_attempted = true;
400                    plugin.stop(context).await.map_err(|error| Error::Plugin(Arc::new(error)))?;
401                }
402                plugin.cleanup().await.map_err(|error| Error::Plugin(Arc::new(error)))
403            })
404            .await;
405            (plugin, result, shutdown_attempted)
406        }));
407        Ok(())
408    }
409
410    async fn finalize(&mut self) -> Result<(), Error<P>> {
411        if self.delivered {
412            return Ok(());
413        }
414        if self.action == lifecycle::Action::Initialize {
415            self.finish().await?;
416            self.action = lifecycle::Action::Shutdown;
417            self.result = None;
418        }
419        if let Some(Err(error)) = &self.result {
420            if !self.failure_reported {
421                self.failure_reported = true;
422                return Err(error.clone());
423            }
424            self.failure_reported = false;
425            self.start_shutdown()?;
426        } else if self.work.is_none() && self.result.is_none() {
427            self.start_shutdown()?;
428        }
429        self.finish().await?;
430        if let Some(Err(error)) = &self.result {
431            self.failure_reported = true;
432            return Err(error.clone());
433        }
434        self.plugin.take();
435        self.slot.lock().map_err(|_| Error::Poisoned)?.phase = Phase::Closed;
436        Ok(())
437    }
438}
439
440/* === Public Functions === */
441
442/* === Private Functions === */
443
444// Guard Only The Borrowed Machinery Future; The Outer Future Retains P
445async fn guarded<P: Plugin>(future: impl Future<Output = Result<(), Error<P>>>) -> Result<(), Error<P>> {
446    let mut future = std::pin::pin!(future);
447    std::future::poll_fn(|context| match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| future.as_mut().poll(context))) {
448        Ok(result) => result,
449        Err(_) => std::task::Poll::Ready(Err(Error::Panicked)),
450    })
451    .await
452}
453
454/* === Tests === */