Skip to main content

liminal_server/server/connection/
supervisor_lifecycle.rs

1use 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    /// 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    ///
61    /// SRV-042 R1: from here until the start publishes, an accepted
62    /// connection is held (see [`Self::admit_when_started`]); the start thread
63    /// runs what was held once it has, before a failed start stops the server,
64    /// so a held client reads the start's refusal. The thread holds the
65    /// supervisor weakly: it never keeps a stopped server alive.
66    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                if let Err(error) = started
88                    && !ConnectionIncarnationAuthority::is_abandoned(&error)
89                {
90                    tracing::error!(%error, "the incarnation start failed behind the bound listener; the server stops");
91                    if let Some(fatal) = fatal {
92                        fatal.initiate();
93                    }
94                }
95            })
96            .map(Some)
97            .map_err(|error| {
98                self.inner.held.stop();
99                refusal(format!("the incarnation start could not begin: {error}"))
100            })
101    }
102
103    /// Runs `admit` for a connection just accepted: at once, or, while the
104    /// incarnation start runs, on the start thread once it has published, so
105    /// the accept thread returns to accepting and a stop never waits on the
106    /// start (SRV-042 R1). After shutdown it is dropped, closing its socket.
107    pub(in crate::server) fn admit_when_started(&self, admit: impl FnOnce(&Self) + Send + 'static) {
108        if let Some(admit) = self.inner.held.hold(Box::new(admit)) {
109            admit(self);
110        }
111    }
112
113    /// The incarnation start's typed error once it has failed, which a server
114    /// stopped by that failure returns; nothing while it has not.
115    #[must_use]
116    pub fn incarnation_start_failure(&self) -> Option<ServerError> {
117        self.inner
118            .incarnations
119            .as_ref()
120            .and_then(|authority| authority.start_failure())
121    }
122
123    pub(in crate::server) fn finish_store_work(&self) -> Result<(), ServerError> {
124        self.inner.finish_store_work()
125    }
126
127    pub(in crate::server) fn release_store_holders(&self) -> Result<(), ServerError> {
128        self.inner.runtime.services().release_store_holders()
129    }
130}
131
132impl SupervisorInner {
133    fn finish_store_work(&self) -> Result<(), ServerError> {
134        #[cfg(test)]
135        let observer = self
136            .reclaim_stop_observer
137            .lock()
138            .map_err(|_| refusal("reclamation stop observer lock poisoned"))?
139            .take();
140        #[cfg(test)]
141        if let Some(observer) = observer {
142            observer
143                .send(true)
144                .map_err(|error| refusal(error.to_string()))?;
145        }
146        self.held.stop();
147        if let Some(authority) = &self.incarnations {
148            authority.abandon();
149        }
150        first_failure([
151            self.reclaim.finish(&self.scheduler),
152            join_background(&self.background),
153            self.runtime.services().finish_background_store_work(),
154        ])
155    }
156}
157
158impl Drop for SupervisorInner {
159    fn drop(&mut self) {
160        self.scheduler.shutdown();
161        if let Err(error) = self.finish_store_work() {
162            tracing::error!(%error, "connection supervisor store teardown failed");
163        }
164    }
165}
166
167fn join_background(background: &Mutex<BackgroundStart>) -> Result<(), ServerError> {
168    let mut start = background
169        .lock()
170        .map_err(|_| refusal("connection background join lock poisoned"))?;
171    start.stopped = true;
172    if let Some(failure) = &start.failure {
173        return Err(refusal(failure.clone()));
174    }
175    let joined = start.worker.take().map_or(Ok(()), |worker| {
176        worker
177            .join()
178            .map_err(|_| refusal("connection background store work panicked during shutdown"))
179    });
180    let started = start.incarnation.take().map_or(Ok(()), |worker| {
181        // The start thread can hold the last owner while it admits what it
182        // held; dropping it there ends here, on the thread itself, which is
183        // finishing: it is not joined from itself.
184        if worker.thread().id() == std::thread::current().id() {
185            return Ok(());
186        }
187        crate::server::boot_steps::stop_timed("incarnation_join", || worker.join())
188            .map_err(|_| refusal("the incarnation start panicked during shutdown"))
189    });
190    let mut results = vec![joined, started];
191    for release in &mut start.reclaim_releases {
192        results.push(release.finish());
193    }
194    start.reclaim_releases.clear();
195    let mut first = None;
196    for result in results {
197        if let Err(error) = result {
198            tracing::error!(%error, "background store teardown failed");
199            if first.is_none() {
200                first = Some(error);
201            }
202        }
203    }
204    if let Some(error) = first {
205        start.failure = Some(error.to_string());
206        return Err(error);
207    }
208    drop(start);
209    Ok(())
210}
211
212fn first_failure<const N: usize>(results: [Result<(), ServerError>; N]) -> Result<(), ServerError> {
213    let mut first = None;
214    for result in results {
215        if let Err(error) = result {
216            tracing::error!(%error, "connection store work teardown failed");
217            if first.is_none() {
218                first = Some(error);
219            }
220        }
221    }
222    first.map_or(Ok(()), Err)
223}
224
225fn refusal(message: impl Into<String>) -> ServerError {
226    ServerError::ListenerAccept {
227        message: message.into(),
228    }
229}