liminal_server/server/connection/
supervisor_lifecycle.rs1use std::sync::{Arc, Mutex};
2use std::thread::JoinHandle;
3
4use super::super::reclaim_placement::ReclaimRelease;
5use super::{ConnectionIncarnationAuthority, 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> {
67 let Some(authority) = self.inner.incarnations.clone() else {
68 return Ok(None);
69 };
70 let fatal = self.inner.runtime.fatal_shutdown.clone();
71 let held = Arc::clone(&self.inner.held);
72 let supervisor = Arc::downgrade(&self.inner);
73 held.starting();
74 std::thread::Builder::new()
75 .name("liminal-incarnation-start".to_owned())
76 .spawn(move || {
77 let started = authority.ensure_started();
78 let admissions = held.started();
79 if !admissions.is_empty()
80 && let Some(inner) = supervisor.upgrade()
81 {
82 let supervisor = Self { inner };
83 for admit in admissions {
84 admit(&supervisor);
85 }
86 }
87 let settled = started.and_then(|()| authority.settle_anchor());
91 if let Err(error) = settled
92 && !ConnectionIncarnationAuthority::is_abandoned(&error)
93 {
94 tracing::error!(%error, "the incarnation start or its anchor swap failed behind the bound listener; the server stops");
95 if let Some(fatal) = fatal {
96 fatal.initiate();
97 }
98 }
99 })
100 .map(Some)
101 .map_err(|error| {
102 self.inner.held.stop();
103 refusal(format!("the incarnation start could not begin: {error}"))
104 })
105 }
106
107 pub(in crate::server) fn admit_when_started(&self, admit: impl FnOnce(&Self) + Send + 'static) {
112 if let Some(admit) = self.inner.held.hold(Box::new(admit)) {
113 admit(self);
114 }
115 }
116
117 #[must_use]
120 pub fn incarnation_start_failure(&self) -> Option<ServerError> {
121 self.inner
122 .incarnations
123 .as_ref()
124 .and_then(|authority| authority.start_failure())
125 }
126
127 pub(in crate::server) fn finish_store_work(&self) -> Result<(), ServerError> {
128 self.inner.finish_store_work()
129 }
130
131 pub(in crate::server) fn release_store_holders(&self) -> Result<(), ServerError> {
132 self.inner.runtime.services().release_store_holders()
133 }
134}
135
136impl SupervisorInner {
137 fn finish_store_work(&self) -> Result<(), ServerError> {
138 #[cfg(test)]
139 let observer = self
140 .reclaim_stop_observer
141 .lock()
142 .map_err(|_| refusal("reclamation stop observer lock poisoned"))?
143 .take();
144 #[cfg(test)]
145 if let Some(observer) = observer {
146 observer
147 .send(true)
148 .map_err(|error| refusal(error.to_string()))?;
149 }
150 self.held.stop();
151 if let Some(authority) = &self.incarnations {
152 authority.abandon();
153 }
154 first_failure([
155 self.reclaim.finish(&self.scheduler),
156 join_background(&self.background),
157 self.runtime.services().finish_background_store_work(),
158 ])
159 }
160}
161
162impl Drop for SupervisorInner {
163 fn drop(&mut self) {
164 self.scheduler.shutdown();
165 if let Err(error) = self.finish_store_work() {
166 tracing::error!(%error, "connection supervisor store teardown failed");
167 }
168 }
169}
170
171fn join_background(background: &Mutex<BackgroundStart>) -> Result<(), ServerError> {
172 let mut start = background
173 .lock()
174 .map_err(|_| refusal("connection background join lock poisoned"))?;
175 start.stopped = true;
176 if let Some(failure) = &start.failure {
177 return Err(refusal(failure.clone()));
178 }
179 let joined = start.worker.take().map_or(Ok(()), |worker| {
180 worker
181 .join()
182 .map_err(|_| refusal("connection background store work panicked during shutdown"))
183 });
184 let started = start.incarnation.take().map_or(Ok(()), |worker| {
185 if worker.thread().id() == std::thread::current().id() {
189 return Ok(());
190 }
191 crate::server::boot_steps::stop_timed("incarnation_join", || worker.join())
192 .map_err(|_| refusal("the incarnation start panicked during shutdown"))
193 });
194 let mut results = vec![joined, started];
195 for release in &mut start.reclaim_releases {
196 results.push(release.finish());
197 }
198 start.reclaim_releases.clear();
199 let mut first = None;
200 for result in results {
201 if let Err(error) = result {
202 tracing::error!(%error, "background store teardown failed");
203 if first.is_none() {
204 first = Some(error);
205 }
206 }
207 }
208 if let Some(error) = first {
209 start.failure = Some(error.to_string());
210 return Err(error);
211 }
212 drop(start);
213 Ok(())
214}
215
216fn first_failure<const N: usize>(results: [Result<(), ServerError>; N]) -> Result<(), ServerError> {
217 let mut first = None;
218 for result in results {
219 if let Err(error) = result {
220 tracing::error!(%error, "connection store work teardown failed");
221 if first.is_none() {
222 first = Some(error);
223 }
224 }
225 }
226 first.map_or(Ok(()), Err)
227}
228
229fn refusal(message: impl Into<String>) -> ServerError {
230 ServerError::ListenerAccept {
231 message: message.into(),
232 }
233}