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 let mut started: Vec<usize> = Vec::new();
49
50 tracing::debug!(hook_count = self.hooks.len(), "lifecycle startup begin");
51 for (index, hook) in self.hooks.iter().enumerate() {
52 tracing::debug!(hook_index = index, "lifecycle startup hook begin");
53 if let Err(source) = hook.on_startup().await {
54 tracing::error!(
55 hook_index = index,
56 error = %source,
57 "lifecycle startup hook failed"
58 );
59 let mut rollback_errors = Vec::new();
60 for started_index in started.into_iter().rev() {
61 tracing::debug!(
62 hook_index = started_index,
63 "lifecycle startup rollback hook begin"
64 );
65 if let Err(error) = self.hooks[started_index].on_shutdown().await {
66 tracing::error!(
67 hook_index = started_index,
68 error = %error,
69 "lifecycle startup rollback hook failed"
70 );
71 rollback_errors.push(error);
72 } else {
73 tracing::debug!(
74 hook_index = started_index,
75 "lifecycle startup rollback hook complete"
76 );
77 }
78 }
79
80 return Err(NidusError::LifecycleStartup {
81 source: Box::new(source),
82 rollback_errors,
83 });
84 }
85 tracing::debug!(hook_index = index, "lifecycle startup hook complete");
86 started.push(index);
87 }
88 tracing::debug!(hook_count = self.hooks.len(), "lifecycle startup complete");
89 Ok(())
90 }
91
92 #[tracing::instrument(
97 name = "lifecycle.shutdown",
98 skip_all,
99 fields(hook_count = self.hooks.len())
100 )]
101 pub async fn shutdown(&self) -> Result<()> {
102 tracing::debug!(hook_count = self.hooks.len(), "lifecycle shutdown begin");
103 let mut first_error = None;
104 let mut error_count = 0_usize;
105 for (index, hook) in self.hooks.iter().enumerate().rev() {
106 tracing::debug!(hook_index = index, "lifecycle shutdown hook begin");
107 if let Err(error) = hook.on_shutdown().await {
108 error_count += 1;
109 tracing::error!(
110 hook_index = index,
111 error = %error,
112 "lifecycle shutdown hook failed"
113 );
114 if first_error.is_none() {
115 first_error = Some(error);
116 }
117 } else {
118 tracing::debug!(hook_index = index, "lifecycle shutdown hook complete");
119 }
120 }
121 if let Some(error) = first_error {
122 tracing::error!(
123 hook_count = self.hooks.len(),
124 error_count,
125 "lifecycle shutdown completed with errors"
126 );
127 return Err(error);
128 }
129 tracing::debug!(hook_count = self.hooks.len(), "lifecycle shutdown complete");
130 Ok(())
131 }
132
133 pub(crate) fn empty() -> Self {
134 Self::new()
135 }
136}