nidus_core/lifecycle/
mod.rs1use crate::{NidusError, Result};
4use async_trait::async_trait;
5
6#[async_trait]
8pub trait LifecycleHook: Send + Sync + 'static {
9 async fn on_startup(&self) -> Result<()> {
11 Ok(())
12 }
13
14 async fn on_shutdown(&self) -> Result<()> {
16 Ok(())
17 }
18}
19
20#[derive(Default)]
22pub struct LifecycleRunner {
23 hooks: Vec<Box<dyn LifecycleHook>>,
24}
25
26impl LifecycleRunner {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn hook<H>(mut self, hook: H) -> Self
34 where
35 H: LifecycleHook,
36 {
37 self.hooks.push(Box::new(hook));
38 self
39 }
40
41 #[tracing::instrument(
43 name = "lifecycle.startup",
44 skip_all,
45 fields(hook_count = self.hooks.len())
46 )]
47 pub async fn startup(&self) -> Result<()> {
48 tracing::debug!(hook_count = self.hooks.len(), "lifecycle startup begin");
49 for (index, hook) in self.hooks.iter().enumerate() {
50 tracing::debug!(hook_index = index, "lifecycle startup hook begin");
51 if let Err(source) = hook.on_startup().await {
52 tracing::error!(
53 hook_index = index,
54 error = %source,
55 "lifecycle startup hook failed"
56 );
57 let mut rollback_errors = Vec::new();
58 for started_index in (0..index).rev() {
60 tracing::debug!(
61 hook_index = started_index,
62 "lifecycle startup rollback hook begin"
63 );
64 if let Err(error) = self.hooks[started_index].on_shutdown().await {
65 tracing::error!(
66 hook_index = started_index,
67 error = %error,
68 "lifecycle startup rollback hook failed"
69 );
70 rollback_errors.push(error);
71 } else {
72 tracing::debug!(
73 hook_index = started_index,
74 "lifecycle startup rollback hook complete"
75 );
76 }
77 }
78
79 return Err(NidusError::LifecycleStartup {
80 source: Box::new(source),
81 rollback_errors,
82 });
83 }
84 tracing::debug!(hook_index = index, "lifecycle startup hook complete");
85 }
86 tracing::debug!(hook_count = self.hooks.len(), "lifecycle startup complete");
87 Ok(())
88 }
89
90 #[tracing::instrument(
95 name = "lifecycle.shutdown",
96 skip_all,
97 fields(hook_count = self.hooks.len())
98 )]
99 pub async fn shutdown(&self) -> Result<()> {
100 tracing::debug!(hook_count = self.hooks.len(), "lifecycle shutdown begin");
101 let mut first_error = None;
102 let mut error_count = 0_usize;
103 for (index, hook) in self.hooks.iter().enumerate().rev() {
104 tracing::debug!(hook_index = index, "lifecycle shutdown hook begin");
105 if let Err(error) = hook.on_shutdown().await {
106 error_count += 1;
107 tracing::error!(
108 hook_index = index,
109 error = %error,
110 "lifecycle shutdown hook failed"
111 );
112 if first_error.is_none() {
113 first_error = Some(error);
114 }
115 } else {
116 tracing::debug!(hook_index = index, "lifecycle shutdown hook complete");
117 }
118 }
119 if let Some(error) = first_error {
120 tracing::error!(
121 hook_count = self.hooks.len(),
122 error_count,
123 "lifecycle shutdown completed with errors"
124 );
125 return Err(error);
126 }
127 tracing::debug!(hook_count = self.hooks.len(), "lifecycle shutdown complete");
128 Ok(())
129 }
130
131 pub(crate) fn empty() -> Self {
132 Self::new()
133 }
134}