Skip to main content

liminal_server/server/connection/
supervisor_lifecycle.rs

1use 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    /// The incarnation start behind the bind (SRV-041 R2).
14    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    /// Begins the incarnation start on its own thread. A start that fails
58    /// stops the server through the fatal shutdown; its typed error is
59    /// answered by [`Self::incarnation_start_failure`].
60    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    /// The incarnation start's typed error once it has failed, which a server
80    /// stopped by that failure returns; nothing while it has not.
81    #[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}