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