liminal_server/server/connection/
supervisor_lifecycle.rs1use std::sync::Mutex;
2use std::thread::JoinHandle;
3
4use super::super::reclaim_placement::ReclaimRelease;
5use super::{ConnectionSupervisor, SupervisorInner};
6use crate::ServerError;
7
8#[derive(Debug, Default)]
9pub(super) struct BackgroundStart {
10 started: bool,
11 stopped: bool,
12 worker: Option<JoinHandle<()>>,
13 incarnation: Option<JoinHandle<()>>,
15 failure: Option<String>,
16 reclaim_releases: Vec<ReclaimRelease>,
17}
18
19impl ConnectionSupervisor {
20 pub(crate) fn retain_reclaim_release(
21 &self,
22 release: ReclaimRelease,
23 ) -> Result<(), ServerError> {
24 let mut start = self
25 .inner
26 .background
27 .lock()
28 .map_err(|_| refusal("connection background start lock poisoned"))?;
29 if start.stopped {
30 return Err(refusal("spine release cannot be registered after shutdown"));
31 }
32 start.reclaim_releases.push(release);
33 drop(start);
34 Ok(())
35 }
36
37 pub(crate) fn listening(&self) -> Result<(), ServerError> {
38 let mut start = self
39 .inner
40 .background
41 .lock()
42 .map_err(|_| refusal("connection background start lock poisoned"))?;
43 if start.stopped {
44 return Err(refusal(
45 "connection background work cannot start after shutdown",
46 ));
47 }
48 if !start.started {
49 start.worker = self.inner.runtime.services_arc().listening();
50 start.incarnation = self.start_incarnation()?;
51 start.started = true;
52 }
53 drop(start);
54 Ok(())
55 }
56
57 fn start_incarnation(&self) -> Result<Option<JoinHandle<()>>, ServerError> {
61 let Some(authority) = self.inner.incarnations.clone() else {
62 return Ok(None);
63 };
64 let fatal = self.inner.runtime.fatal_shutdown.clone();
65 std::thread::Builder::new()
66 .name("liminal-incarnation-start".to_owned())
67 .spawn(move || {
68 if let Err(error) = authority.ensure_started() {
69 tracing::error!(%error, "the incarnation start failed behind the bound listener; the server stops");
70 if let Some(fatal) = fatal {
71 fatal.initiate();
72 }
73 }
74 })
75 .map(Some)
76 .map_err(|error| refusal(format!("the incarnation start could not begin: {error}")))
77 }
78
79 #[must_use]
82 pub fn incarnation_start_failure(&self) -> Option<ServerError> {
83 self.inner
84 .incarnations
85 .as_ref()
86 .and_then(|authority| authority.start_failure())
87 }
88
89 pub(in crate::server) fn finish_store_work(&self) -> Result<(), ServerError> {
90 self.inner.finish_store_work()
91 }
92
93 pub(in crate::server) fn release_store_holders(&self) -> Result<(), ServerError> {
94 self.inner.runtime.services().release_store_holders()
95 }
96}
97
98impl SupervisorInner {
99 fn finish_store_work(&self) -> Result<(), ServerError> {
100 #[cfg(test)]
101 let observer = self
102 .reclaim_stop_observer
103 .lock()
104 .map_err(|_| refusal("reclamation stop observer lock poisoned"))?
105 .take();
106 #[cfg(test)]
107 if let Some(observer) = observer {
108 observer
109 .send(true)
110 .map_err(|error| refusal(error.to_string()))?;
111 }
112 first_failure([
113 self.reclaim.finish(&self.scheduler),
114 join_background(&self.background),
115 self.runtime.services().finish_background_store_work(),
116 ])
117 }
118}
119
120impl Drop for SupervisorInner {
121 fn drop(&mut self) {
122 self.scheduler.shutdown();
123 if let Err(error) = self.finish_store_work() {
124 tracing::error!(%error, "connection supervisor store teardown failed");
125 }
126 }
127}
128
129fn join_background(background: &Mutex<BackgroundStart>) -> Result<(), ServerError> {
130 let mut start = background
131 .lock()
132 .map_err(|_| refusal("connection background join lock poisoned"))?;
133 start.stopped = true;
134 if let Some(failure) = &start.failure {
135 return Err(refusal(failure.clone()));
136 }
137 let joined = start.worker.take().map_or(Ok(()), |worker| {
138 worker
139 .join()
140 .map_err(|_| refusal("connection background store work panicked during shutdown"))
141 });
142 let started = start.incarnation.take().map_or(Ok(()), |worker| {
143 worker
144 .join()
145 .map_err(|_| refusal("the incarnation start panicked during shutdown"))
146 });
147 let mut results = vec![joined, started];
148 for release in &mut start.reclaim_releases {
149 results.push(release.finish());
150 }
151 start.reclaim_releases.clear();
152 let mut first = None;
153 for result in results {
154 if let Err(error) = result {
155 tracing::error!(%error, "background store teardown failed");
156 if first.is_none() {
157 first = Some(error);
158 }
159 }
160 }
161 if let Some(error) = first {
162 start.failure = Some(error.to_string());
163 return Err(error);
164 }
165 drop(start);
166 Ok(())
167}
168
169fn first_failure<const N: usize>(results: [Result<(), ServerError>; N]) -> Result<(), ServerError> {
170 let mut first = None;
171 for result in results {
172 if let Err(error) = result {
173 tracing::error!(%error, "connection store work teardown failed");
174 if first.is_none() {
175 first = Some(error);
176 }
177 }
178 }
179 first.map_or(Ok(()), Err)
180}
181
182fn refusal(message: impl Into<String>) -> ServerError {
183 ServerError::ListenerAccept {
184 message: message.into(),
185 }
186}