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                // SRV-048 R2: the anchor swap is settled here, after the
88                // held connections are admitted; a refused swap stops the
89                // server as a failed start does.
90                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    /// Runs `admit` for a connection just accepted: at once, or, while the
108    /// incarnation start runs, on the start thread once it has published, so
109    /// the accept thread returns to accepting and a stop never waits on the
110    /// start (SRV-042 R1). After shutdown it is dropped, closing its socket.
111    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    /// The incarnation start's typed error once it has failed, which a server
118    /// stopped by that failure returns; nothing while it has not.
119    #[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        // The start thread can hold the last owner while it admits what it
186        // held; dropping it there ends here, on the thread itself, which is
187        // finishing: it is not joined from itself.
188        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}