1use std::future::Future;
18use std::num::NonZeroUsize;
19use std::pin::Pin;
20use std::sync::{Arc, Mutex, Weak};
21use super::{Attachment, Context as PluginContext, Error, Plugin};
25use crate::Setup;
26use crate::operations::{Contract, ExecutionContext, ServingError, StartupError, WireTable};
27use harmos_domain::model::operations::lifecycle;
28
29pub type Table = Arc<Mutex<WireTable>>;
36type Transition<P> = Pin<Box<dyn Future<Output = Completed<P>> + Send>>;
37
38pub struct Owner<P: Plugin> {
41 slot: Arc<Mutex<Slot<P>>>,
42 table: Weak<Mutex<WireTable>>,
43 contracts: Arc<[Contract]>,
44 observers: Arc<[crate::inventory::observers::Declaration]>,
45 retirement: Mutex<Retirement<P>>,
46}
47
48struct LifecycleInvocation<P: Plugin> {
49 owner: Arc<Owner<P>>,
50 state: Option<State<P>>,
51 action: lifecycle::Action,
52}
53
54struct Retirement<P: Plugin> {
55 state: Option<State<P>>,
56 active: bool,
57 finished: bool,
58}
59
60struct RetirementLoan<P: Plugin> {
61 owner: Arc<Owner<P>>,
62 state: Option<State<P>>,
63 finished: bool,
64}
65
66struct Slot<P: Plugin> {
67 plugin: Option<P>,
68 context: Option<P::Context>,
69 phase: Phase,
70 attachment: Option<Arc<dyn Attachment<P::Context>>>,
71 generation_context: Option<[u8; 32]>,
72}
73
74struct State<P: Plugin> {
75 slot: Arc<Mutex<Slot<P>>>,
76 table: Weak<Mutex<WireTable>>,
77 plugin: Option<P>,
78 config: Option<P::Config>,
79 configuration: Option<WireTable>,
80 work: Option<Transition<P>>,
81 result: Option<Result<(), Error<P>>>,
82 action: lifecycle::Action,
83 delivered: bool,
84 failure_reported: bool,
85 shutdown_attempted: bool,
86 cleanup_finished: bool,
87 context: Option<P::Context>,
88 assignments: Vec<u8>,
89 attachment: Option<Arc<dyn Attachment<P::Context>>>,
90 generation_context: Option<[u8; 32]>,
91}
92
93struct Completed<P: Plugin> {
94 plugin: Option<P>,
95 context: Option<P::Context>,
96 configuration: Option<WireTable>,
97 attachment: Option<Arc<dyn Attachment<P::Context>>>,
98 result: Result<(), Error<P>>,
99 shutdown_attempted: bool,
100 cleanup_finished: bool,
101}
102
103#[derive(Clone, Copy, PartialEq, Eq)]
106enum Phase {
107 Empty,
108 Initializing,
109 Initialized,
110 Stopping,
111 Closed,
112}
113
114impl<P: Plugin> Owner<P> {
119 pub fn new(owner: &str, contracts: Vec<Contract>, capacity: NonZeroUsize) -> Result<(Arc<Self>, Table), Error<P>> {
120 Self::new_with_observers(owner, contracts, capacity, Vec::new())
121 }
122
123 pub fn new_with_observers(
124 assigned_owner: &str, contracts: Vec<Contract>, capacity: NonZeroUsize, observers: Vec<crate::inventory::observers::Declaration>,
125 ) -> Result<(Arc<Self>, Table), Error<P>> {
126 let definitions: Vec<_> = contracts.iter().map(|contract| contract.operation().clone()).collect();
127 crate::inventory::operations::validate(&definitions).map_err(|error| Error::Inventory(Arc::new(error)))?;
128 let mut serving = WireTable::new(assigned_owner).map_err(|error| Error::Serving(Arc::new(error)))?;
129 serving.capacity(capacity);
130 serving.provide(std::any::TypeId::of::<P::Context>());
131 serving.provide(std::any::TypeId::of::<crate::operations::Calls>());
132 let table = Arc::new(Mutex::new(serving));
133 let owner = Arc::new(Self {
134 slot: Arc::new(Mutex::new(Slot {
135 plugin: None,
136 context: None,
137 phase: Phase::Empty,
138 attachment: None,
139 generation_context: None,
140 })),
141 table: Arc::downgrade(&table),
142 contracts: contracts.into(),
143 observers: observers.into(),
144 retirement: Mutex::new(Retirement {
145 state: None,
146 active: false,
147 finished: false,
148 }),
149 });
150 Ok((owner, table))
151 }
152
153 pub async fn quiesce(&self) -> Result<(), Error<P>> {
154 let attachment = self.slot.lock().map_err(|_| Error::Poisoned)?.attachment.clone();
155 if let Some(attachment) = attachment {
156 guarded(async { attachment.quiesce().await.map_err(|error| Error::Serving(Arc::new(error))) }).await?;
157 }
158 Ok(())
159 }
160
161 pub async fn retire(self: &Arc<Self>) -> Result<(), Error<P>> {
162 let table = self.table.upgrade().ok_or(Error::Phase)?;
163 {
164 let table = table.lock().map_err(|_| Error::Poisoned)?;
165 if !table.is_settled() {
166 return Err(Error::Phase);
167 }
168 }
169 let mut loan = {
170 let mut retirement = self.retirement.lock().map_err(|_| Error::Poisoned)?;
171 if retirement.finished {
172 return Ok(());
173 }
174 if retirement.active {
175 return Err(Error::Phase);
176 }
177 let state = match retirement.state.take() {
178 Some(state) => state,
179 None => self.prepare_shutdown()?,
180 };
181 retirement.active = true;
182 RetirementLoan {
183 owner: Arc::clone(self),
184 state: Some(state),
185 finished: false,
186 }
187 };
188 loan.state.as_mut().ok_or(Error::Phase)?.finalize().await?;
189 loan.finished = true;
190 Ok(())
191 }
192
193 fn state(&self, plugin: Option<P>, action: lifecycle::Action) -> State<P> {
194 State {
195 slot: Arc::clone(&self.slot),
196 table: self.table.clone(),
197 plugin,
198 config: None,
199 configuration: None,
200 work: None,
201 result: None,
202 action,
203 delivered: false,
204 failure_reported: false,
205 shutdown_attempted: false,
206 cleanup_finished: false,
207 context: None,
208 assignments: Vec::new(),
209 attachment: None,
210 generation_context: None,
211 }
212 }
213}
214
215impl<P: Plugin> Drop for RetirementLoan<P> {
216 fn drop(&mut self) {
217 let mut retirement = self.owner.retirement.lock().unwrap_or_else(|error| error.into_inner());
218 retirement.active = false;
219 retirement.finished = self.finished;
220 if !self.finished {
221 retirement.state = self.state.take();
222 }
223 }
224}
225
226impl<P: Plugin> Owner<P> {
227 fn prepare_initialize(&self, config_json: &[u8], generation_context: &[u8], assignments: &[u8]) -> Result<State<P>, Error<P>> {
228 let generation_context = if generation_context.is_empty() {
229 None
230 } else {
231 let token: [u8; 32] = generation_context.try_into().map_err(|_| Error::GenerationContext)?;
232 if token == [0; 32] {
233 return Err(Error::GenerationContext);
234 }
235 Some(token)
236 };
237 let config = crate::decode_config(config_json).map_err(|error| Error::Configuration(Arc::new(error)))?;
238 let mut slot = self.slot.lock().map_err(|_| Error::Poisoned)?;
239 if slot.phase != Phase::Empty {
240 return Err(Error::Phase);
241 }
242 slot.phase = Phase::Initializing;
243 let mut state = self.state(None, lifecycle::Action::Initialize);
244 state.config = Some(config);
245 state.generation_context = generation_context;
246 state.assignments = assignments.to_vec();
247 Ok(state)
248 }
249
250 async fn initialize(&self, state: &mut State<P>, context: &P::Context) -> Result<(), Error<P>> {
251 if state.work.is_none() && state.result.is_none() {
252 context.assign_operations(&state.assignments).map_err(|error| Error::Serving(Arc::new(error)))?;
253 let config = state.config.take().ok_or(Error::Phase)?;
254 let configuration = {
255 let table = self.table.upgrade().ok_or(Error::Phase)?;
256 let table = table.lock().map_err(|_| Error::Poisoned)?;
257 table.configuration().map_err(|error| Error::Serving(Arc::new(error)))?
258 };
259 let context = state.generation_context.map_or_else(|| context.clone(), |token| context.with_generation(token));
260 state.work = Some(Box::pin(prepare::<P>(
261 config,
262 configuration,
263 context,
264 self.contracts.clone(),
265 self.observers.clone(),
266 state.assignments.clone(),
267 )));
268 }
269 state.finish().await?;
270 state.result.as_ref().ok_or(Error::Phase)?.clone()?;
271 if state.delivered {
272 return Ok(());
273 }
274 let attachment = state.attachment.clone();
275 let table = self.table.upgrade().ok_or(Error::Phase)?;
276 let binding = {
277 let mut table = table.lock().map_err(|_| Error::Poisoned)?;
278 let configuration = state.configuration.take().ok_or(Error::Phase)?;
279 table.install_configuration(configuration).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.context = state.context.take();
288 slot.attachment = attachment;
289 slot.generation_context = state.generation_context;
290 slot.phase = Phase::Initialized;
291 state.delivered = true;
292 Ok(())
293 }
294
295 fn prepare_shutdown(&self) -> Result<State<P>, Error<P>> {
296 let mut slot = self.slot.lock().map_err(|_| Error::Poisoned)?;
297 if matches!(slot.phase, Phase::Empty | Phase::Closed) {
298 let mut state = self.state(None, lifecycle::Action::Shutdown);
299 state.result = Some(Ok(()));
300 return Ok(state);
301 }
302 if slot.phase != Phase::Initialized {
303 return Err(Error::Phase);
304 }
305 let plugin = slot.plugin.take().ok_or(Error::Phase)?;
306 slot.phase = Phase::Stopping;
307 let mut state = self.state(Some(plugin), lifecycle::Action::Shutdown);
308 state.attachment = slot.attachment.take();
309 state.context = slot.context.take();
310 state.generation_context = slot.generation_context;
311 Ok(state)
312 }
313}
314
315impl<P: Plugin> crate::operations::lifecycle::PluginLifecycle for Owner<P> {
316 fn begin(self: Arc<Self>, action: lifecycle::Action) -> Box<dyn crate::operations::lifecycle::LifecycleInstance> {
317 Box::new(LifecycleInvocation { owner: self, state: None, action })
318 }
319}
320impl<P: Plugin> crate::operations::lifecycle::LifecycleInstance for LifecycleInvocation<P> {
321 fn run<'a>(
322 &'a mut self, context: ExecutionContext, config: &'a [u8], generation: &'a [u8], assignments: &'a [u8],
323 ) -> crate::operations::lifecycle::Transition<'a> {
324 Box::pin(async move {
325 let result: Result<(), Error<P>> = async {
326 match self.action {
327 lifecycle::Action::Initialize => {
328 if self.state.is_none() {
329 self.state = Some(self.owner.prepare_initialize(config, generation, assignments)?);
330 }
331 let carrier = context.resource::<P::Context>().map_err(|error| Error::Resource(Arc::new(error)))?;
332 self.owner.initialize(self.state.as_mut().ok_or(Error::Phase)?, &carrier).await
333 }
334 lifecycle::Action::Shutdown => {
335 if self.state.is_none() {
336 self.state = Some(self.owner.prepare_shutdown()?);
337 }
338 let state = self.state.as_mut().ok_or(Error::Phase)?;
339 if state.work.is_none() && state.result.is_none() {
340 let carrier = context.resource::<P::Context>().map_err(|error| Error::Resource(Arc::new(error)))?;
341 state.context =
342 Some(state.generation_context.map_or_else(|| (*carrier).clone(), |token| carrier.with_generation(token)));
343 state.start_shutdown()?;
344 }
345 state.finish().await?;
346 state.result.as_ref().ok_or(Error::Phase)?.clone()
347 }
348 }
349 }
350 .await;
351 result.map_err(Into::into)
352 })
353 }
354 fn cleanup(&mut self) -> crate::operations::lifecycle::Transition<'_> {
355 Box::pin(async {
356 match self.state.as_mut() {
357 Some(state) => state.finalize().await.map_err(Into::into),
358 None => Ok(()),
359 }
360 })
361 }
362}
363
364impl<P: Plugin> State<P> {
365 async fn finish(&mut self) -> Result<(), Error<P>> {
366 if let Some(work) = &mut self.work {
367 let completed = work.await;
368 self.shutdown_attempted = completed.shutdown_attempted;
369 self.cleanup_finished = completed.cleanup_finished;
370 self.plugin = completed.plugin;
371 if completed.context.is_some() {
372 self.context = completed.context;
373 }
374 self.result = Some(completed.result);
375 if completed.configuration.is_some() {
376 self.configuration = completed.configuration;
377 }
378 if completed.attachment.is_some() {
379 self.attachment = completed.attachment;
380 }
381 self.work = None;
382 }
383 Ok(())
384 }
385
386 fn start_shutdown(&mut self) -> Result<(), Error<P>> {
387 let attachment = self.attachment.clone().or_else(|| self.plugin.as_ref().and_then(Plugin::attachment));
388 let mut plugin = self.plugin.take().ok_or(Error::Phase)?;
389 let table = self.table.clone();
390 let context = self.context.clone();
391 let mut shutdown_attempted = self.shutdown_attempted;
392 let mut cleanup_finished = self.cleanup_finished;
393 self.result = None;
394 self.work = Some(Box::pin(async move {
395 let result = guarded(async {
396 if let Some(attachment) = attachment {
397 attachment.quiesce().await.map_err(|error| Error::Serving(Arc::new(error)))?;
398 }
399 let table = table.upgrade().ok_or(Error::Phase)?;
400 let settlement = {
401 let mut table = table.lock().map_err(|_| Error::Poisoned)?;
402 table.close_ordinary()
403 };
404 settlement.await.map_err(|error| Error::Serving(Arc::new(error)))?;
405 let retired = {
406 let mut table = table.lock().map_err(|_| Error::Poisoned)?;
407 table.release_ordinary().map_err(|error| Error::Serving(Arc::new(error)))?
408 };
409 drop(retired);
410 let mut errors = Vec::new();
411 if !shutdown_attempted && let Some(context) = &context {
412 shutdown_attempted = true;
414 if let Err(error) = guarded(async { plugin.stop(context).await.map_err(|error| Error::Plugin(Arc::new(error))) }).await
415 {
416 errors.push(error);
417 }
418 }
419 if !cleanup_finished {
420 match guarded(async { plugin.cleanup().await.map_err(|error| Error::Plugin(Arc::new(error))) }).await {
421 Ok(()) => cleanup_finished = true,
422 Err(error) => errors.push(error),
423 }
424 }
425 combine(errors)
426 })
427 .await;
428 Completed {
429 plugin: Some(plugin),
430 context: None,
431 configuration: None,
432 attachment: None,
433 result,
434 shutdown_attempted,
435 cleanup_finished,
436 }
437 }));
438 Ok(())
439 }
440
441 async fn finalize(&mut self) -> Result<(), Error<P>> {
442 if self.delivered {
443 return Ok(());
444 }
445 if self.action == lifecycle::Action::Initialize {
446 self.finish().await?;
447 self.action = lifecycle::Action::Shutdown;
448 self.result = None;
449 }
450 if self.plugin.is_none() {
451 self.configuration.take();
452 self.slot.lock().map_err(|_| Error::Poisoned)?.phase = Phase::Closed;
453 return Ok(());
454 }
455 if let Some(Err(error)) = &self.result {
456 if !self.failure_reported {
457 self.failure_reported = true;
458 return Err(error.clone());
459 }
460 self.failure_reported = false;
461 self.start_shutdown()?;
462 } else if self.work.is_none() && self.result.is_none() {
463 self.start_shutdown()?;
464 }
465 self.finish().await?;
466 if let Some(Err(error)) = &self.result {
467 self.failure_reported = true;
468 return Err(error.clone());
469 }
470 self.plugin.take();
471 self.slot.lock().map_err(|_| Error::Poisoned)?.phase = Phase::Closed;
472 Ok(())
473 }
474}
475
476async fn prepare<P: Plugin>(
481 config: P::Config, configuration: WireTable, context: P::Context, contracts: Arc<[Contract]>,
482 observers: Arc<[crate::inventory::observers::Declaration]>, assignments: Vec<u8>,
483) -> Completed<P> {
484 let mut setup = Setup::<P>::new(configuration);
485 for catalog in P::CATALOGS {
486 setup.settings().catalog(catalog);
487 }
488 let prepared = guarded::<P, _>(async { P::prepare(config, &mut setup).await.map_err(|error| Error::Plugin(Arc::new(error))) }).await;
489 let mut errors = Vec::new();
490 let mut plugin = match prepared {
491 Ok(plugin) => Some(plugin),
492 Err(source) => {
493 errors.push(StartupError::Runtime { source: Box::new(source) });
494 None
495 }
496 };
497
498 let mut attachment = None;
499 let binding = guarded::<P, _>(async {
500 if let Some(plugin) = &plugin {
501 attachment = plugin.attachment();
502 let actual = attachment.as_ref().map_or_else(Vec::new, |attachment| attachment.observers());
503 if actual.as_slice() != observers.as_ref() {
504 return Err(Error::Serving(Arc::new(ServingError::Catalog)));
505 }
506 if let Some(attachment) = &attachment {
507 attachment.clone().bind(setup.settings()).map_err(|error| Error::Serving(Arc::new(error)))?;
508 }
509 }
510 Ok(())
511 })
512 .await;
513 if let Err(source) = binding {
514 errors.push(StartupError::Runtime { source: Box::new(source) });
515 }
516 if let Err(error) = setup.validate() {
517 match error {
518 StartupError::Multiple { errors: configuration } => errors.extend(configuration),
519 error => errors.push(error),
520 }
521 }
522 let configuration = setup.into_configuration();
523 if errors.is_empty() {
524 if let Err(source) = configuration.validate_contracts(&contracts) {
525 errors.push(StartupError::Binding { source: Box::new(source) });
526 }
527 if let Err(source) = configuration.validate_callees(&assignments) {
528 errors.push(StartupError::Binding { source: Box::new(source) });
529 }
530 }
531 let result = match StartupError::aggregate(errors) {
532 Some(error) => Err(Error::Serving(Arc::new(ServingError::Startup(Box::new(error))))),
533 None => {
534 let plugin = plugin.as_mut().expect("successful preparation retains the plugin owner");
535 guarded(async { plugin.initialize(&context).await.map_err(|error| Error::Plugin(Arc::new(error))) }).await
536 }
537 };
538 let context = result.is_ok().then_some(context);
539 Completed {
540 plugin,
541 context,
542 configuration: Some(configuration),
543 attachment,
544 result,
545 shutdown_attempted: false,
546 cleanup_finished: false,
547 }
548}
549
550fn combine<P: Plugin>(mut errors: Vec<Error<P>>) -> Result<(), Error<P>> {
551 if errors.len() == 1 {
552 return Err(errors.pop().expect("one lifecycle error"));
553 }
554 let errors = errors.into_iter().map(|source| StartupError::Runtime { source: Box::new(source) }).collect();
555 match StartupError::aggregate(errors) {
556 None => Ok(()),
557 Some(error) => Err(Error::Serving(Arc::new(ServingError::Startup(Box::new(error))))),
558 }
559}
560
561async fn guarded<P: Plugin, T>(future: impl Future<Output = Result<T, Error<P>>>) -> Result<T, Error<P>> {
563 let mut future = std::pin::pin!(future);
564 std::future::poll_fn(|context| match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| future.as_mut().poll(context))) {
565 Ok(result) => result,
566 Err(_) => std::task::Poll::Ready(Err(Error::Panicked)),
567 })
568 .await
569}
570
571#[cfg(test)]
574mod tests {
575 use super::*;
576 use crate::Message;
577 use crate::operations::{OperationCatalog, RegistrationBatch, WireDeclaration};
578
579 struct Events(Arc<Mutex<Vec<&'static str>>>);
580 struct ServedCatalog;
581
582 static SERVED: [crate::catalog::Node<WireDeclaration>; 1] = [crate::catalog::Node::Leaf(WireDeclaration::of::<Serve>())];
583
584 impl OperationCatalog for ServedCatalog {
585 const CATALOG: crate::operations::Catalog = crate::operations::Catalog::of::<Self>("ServedCatalog", &SERVED, &[]);
586
587 fn batch() -> RegistrationBatch {
588 RegistrationBatch::new().operation::<Serve>()
589 }
590 }
591
592 #[crate::operation(harmos_path = crate, resources = [Events, u32], id = "serve", response = u32,
593 policy = <TestPlugin as lifecycle::Policy>::INITIALIZATION_POLICY)]
594 struct Serve;
595
596 impl crate::Operation for Serve {
597 type Error = std::convert::Infallible;
598
599 async fn run(self, context: crate::Context<'_, Self>) -> Result<crate::Response<u32>, Self::Error> {
600 context.resource::<Events>().0.lock().unwrap().push("serve");
601 Ok(crate::Response::from_owner(context.resource::<u32>()))
602 }
603 }
604
605 fn contracts() -> Vec<Contract> {
606 vec![WireDeclaration::of::<Serve>().contract().unwrap()]
607 }
608
609 #[derive(Default, serde::Deserialize)]
610 #[serde(default)]
611 struct Config {
612 #[serde(skip)]
613 events: Arc<Mutex<Vec<&'static str>>>,
614
615 value: u32,
616 duplicate: bool,
617 fail_prepare: bool,
618 fail_stop: bool,
619 fail_cleanup: bool,
620 }
621
622 struct TestPlugin(Config);
623
624 impl lifecycle::Policy for TestPlugin {
625 const INITIALIZATION_POLICY: crate::OperationPolicy = crate::OperationPolicy {
626 timeout: std::time::Duration::from_secs(1),
627 attempt_timeout: std::time::Duration::from_secs(1),
628 retries: crate::RetryPolicy::Never,
629 };
630 const SHUTDOWN_POLICY: crate::OperationPolicy = Self::INITIALIZATION_POLICY;
631 }
632
633 impl crate::Lifecycle for TestPlugin {
634 type Config = Config;
635 type Error = std::io::Error;
636 type Context = ();
637
638 async fn prepare(config: Config, setup: &mut Setup<Self>) -> Result<Self, Self::Error> {
639 config.events.lock().unwrap().push("prepare");
640 setup.resource(Events(config.events.clone()));
641 setup.resource(config.value);
642 if config.duplicate {
643 setup.resource(config.value + 1);
644 }
645 if config.fail_prepare {
646 return Err(std::io::Error::other("prepare failure"));
647 }
648 Ok(Self(config))
649 }
650
651 async fn initialize(&mut self, _: &()) -> Result<(), Self::Error> {
652 self.0.events.lock().unwrap().push("initialize");
653 Ok(())
654 }
655
656 async fn stop(&mut self, _: &()) -> Result<(), Self::Error> {
657 self.0.events.lock().unwrap().push("stop");
658 if self.0.fail_stop {
659 return Err(std::io::Error::other("stop failure"));
660 }
661 Ok(())
662 }
663
664 async fn cleanup(&mut self) -> Result<(), Self::Error> {
665 self.0.events.lock().unwrap().push("cleanup");
666 if self.0.fail_cleanup {
667 return Err(std::io::Error::other("cleanup failure"));
668 }
669 Ok(())
670 }
671 }
672
673 impl Plugin for TestPlugin {
674 const CATALOGS: &'static [crate::operations::Catalog] = &[ServedCatalog::CATALOG];
675 }
676
677 fn ready<T>(future: impl Future<Output = T>) -> T {
678 let mut future = std::pin::pin!(future);
679 let mut context = std::task::Context::from_waker(std::task::Waker::noop());
680 match future.as_mut().poll(&mut context) {
681 std::task::Poll::Ready(value) => value,
682 std::task::Poll::Pending => panic!("the lifecycle fixture completes synchronously"),
683 }
684 }
685
686 fn prepared(config: Config) -> Completed<TestPlugin> {
687 ready(prepare::<TestPlugin>(config, WireTable::new("fixture").unwrap(), (), contracts().into(), Arc::from([]), Vec::new()))
688 }
689
690 #[test]
691 fn validates_before_initialize() {
692 let events = Arc::default();
693 let valid = prepared(Config {
694 events: Arc::clone(&events),
695 ..Default::default()
696 });
697 assert!(valid.result.is_ok());
698 assert_eq!(*events.lock().unwrap(), ["prepare", "initialize"]);
699
700 events.lock().unwrap().clear();
701 let invalid = prepared(Config {
702 events: Arc::clone(&events),
703 duplicate: true,
704 ..Default::default()
705 });
706 assert!(invalid.result.is_err());
707 assert!(invalid.plugin.is_some());
708 assert_eq!(*events.lock().unwrap(), ["prepare"]);
709 }
710
711 #[test]
712 fn retained_owner_lifecycle() {
713 let (owner, table) = Owner::<TestPlugin>::new("fixture", contracts(), NonZeroUsize::MIN).unwrap();
714 let mut state = owner.prepare_initialize(b"{}", &[], &[]).unwrap();
715 ready(owner.initialize(&mut state, &())).unwrap();
716 let events = {
717 let slot = owner.slot.lock().unwrap();
718 assert!(slot.context.is_some());
719 slot.plugin.as_ref().unwrap().0.events.clone()
720 };
721 assert_eq!(*events.lock().unwrap(), ["prepare", "initialize"]);
722
723 {
724 let mut table = table.lock().unwrap();
725 let context = table.context();
726 table.prepare(1, "fixture.serve", Serve::definition().fingerprint.0, 1, Serve.encode()).unwrap();
727 ready(table.bind(1, &context)).unwrap();
728 assert_eq!(ready(table.attempt(1, &context)).unwrap(), 0u32.encode());
729 ready(table.finalize(1)).unwrap();
730 ready(table.settle()).unwrap();
731 }
732 ready(owner.retire()).unwrap();
733 assert_eq!(*events.lock().unwrap(), ["prepare", "initialize", "serve", "stop", "cleanup"]);
734 }
735
736 #[test]
737 fn failed_prepare_aggregates() {
738 let completed = prepared(Config {
739 fail_prepare: true,
740 duplicate: true,
741 ..Default::default()
742 });
743 let Err(Error::Serving(error)) = completed.result else {
744 panic!("preparation and setup failures must be reported");
745 };
746 let ServingError::Startup(error) = error.as_ref() else {
747 panic!("setup failures retain their typed source");
748 };
749 let StartupError::Multiple { errors } = error.as_ref() else {
750 panic!("both independent failures must be retained");
751 };
752 assert_eq!(errors.len(), 2);
753 assert!(errors.iter().any(|error| matches!(error, StartupError::DuplicateResource { .. })));
754 assert!(completed.plugin.is_none());
755 }
756
757 #[test]
758 fn setup_replaces_resource() {
759 let mut setup = Setup::<TestPlugin>::new(WireTable::new("fixture").unwrap());
760 let _plugin = ready(<TestPlugin as crate::Lifecycle>::prepare(Config { value: 1, ..Default::default() }, &mut setup)).unwrap();
761 let replacement = Arc::new(2u32);
762 setup.overrides(|setup| {
763 setup.resource::<u32>(replacement.clone());
764 });
765 setup.validate().unwrap();
766 let actual = setup.settings().context().resource::<u32>().unwrap();
767 assert!(Arc::ptr_eq(&replacement, &actual));
768 }
769
770 #[test]
771 fn stop_failure_cleanup() {
772 let events = Arc::default();
773 let plugin = TestPlugin(Config {
774 events: Arc::clone(&events),
775 fail_stop: true,
776 ..Default::default()
777 });
778 let (owner, _table) = Owner::new("fixture", Vec::new(), NonZeroUsize::MIN).unwrap();
779 let mut state = owner.state(Some(plugin), lifecycle::Action::Shutdown);
780 state.context = Some(());
781 state.start_shutdown().unwrap();
782 ready(state.finish()).unwrap();
783 assert!(state.result.as_ref().unwrap().is_err());
784 assert_eq!(*events.lock().unwrap(), ["stop", "cleanup"]);
785
786 assert!(ready(state.finalize()).is_err());
787 ready(state.finalize()).unwrap();
788 assert_eq!(*events.lock().unwrap(), ["stop", "cleanup"]);
789 }
790
791 #[test]
792 fn retirement_reports_failures() {
793 let plugin = TestPlugin(Config {
794 fail_stop: true,
795 fail_cleanup: true,
796 ..Default::default()
797 });
798 let (owner, _table) = Owner::new("fixture", Vec::new(), NonZeroUsize::MIN).unwrap();
799 let mut state = owner.state(Some(plugin), lifecycle::Action::Shutdown);
800 state.context = Some(());
801 state.start_shutdown().unwrap();
802 ready(state.finish()).unwrap();
803
804 let Err(Error::Serving(error)) = state.result.take().unwrap() else {
805 panic!("both lifecycle failures must remain available");
806 };
807 let ServingError::Startup(error) = error.as_ref() else {
808 panic!("lifecycle errors retain an aggregate source");
809 };
810 let StartupError::Multiple { errors } = error.as_ref() else {
811 panic!("both lifecycle failures must remain available");
812 };
813 assert_eq!(errors.len(), 2);
814 }
815}