use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use super::super::reclaim_placement::ReclaimRelease;
use super::{ConnectionIncarnationAuthority, ConnectionSupervisor, SupervisorInner};
use crate::ServerError;
#[derive(Debug, Default)]
pub(super) struct BackgroundStart {
started: bool,
stopped: bool,
worker: Option<JoinHandle<()>>,
incarnation: Option<JoinHandle<()>>,
failure: Option<String>,
reclaim_releases: Vec<ReclaimRelease>,
}
impl ConnectionSupervisor {
pub(crate) fn retain_reclaim_release(
&self,
release: ReclaimRelease,
) -> Result<(), ServerError> {
let mut start = self
.inner
.background
.lock()
.map_err(|_| refusal("connection background start lock poisoned"))?;
if start.stopped {
return Err(refusal("spine release cannot be registered after shutdown"));
}
start.reclaim_releases.push(release);
drop(start);
Ok(())
}
pub(crate) fn listening(&self) -> Result<(), ServerError> {
let mut start = self
.inner
.background
.lock()
.map_err(|_| refusal("connection background start lock poisoned"))?;
if start.stopped {
return Err(refusal(
"connection background work cannot start after shutdown",
));
}
if !start.started {
start.worker = self.inner.runtime.services_arc().listening();
start.incarnation = self.start_incarnation()?;
start.started = true;
}
drop(start);
Ok(())
}
fn start_incarnation(&self) -> Result<Option<JoinHandle<()>>, ServerError> {
let Some(authority) = self.inner.incarnations.clone() else {
return Ok(None);
};
let fatal = self.inner.runtime.fatal_shutdown.clone();
let held = Arc::clone(&self.inner.held);
let supervisor = Arc::downgrade(&self.inner);
held.starting();
std::thread::Builder::new()
.name("liminal-incarnation-start".to_owned())
.spawn(move || {
let started = authority.ensure_started();
let admissions = held.started();
if !admissions.is_empty()
&& let Some(inner) = supervisor.upgrade()
{
let supervisor = Self { inner };
for admit in admissions {
admit(&supervisor);
}
}
let settled = started.and_then(|()| authority.settle_anchor());
if let Err(error) = settled
&& !ConnectionIncarnationAuthority::is_abandoned(&error)
{
tracing::error!(%error, "the incarnation start or its anchor swap failed behind the bound listener; the server stops");
if let Some(fatal) = fatal {
fatal.initiate();
}
}
})
.map(Some)
.map_err(|error| {
self.inner.held.stop();
refusal(format!("the incarnation start could not begin: {error}"))
})
}
pub(in crate::server) fn admit_when_started(&self, admit: impl FnOnce(&Self) + Send + 'static) {
if let Some(admit) = self.inner.held.hold(Box::new(admit)) {
admit(self);
}
}
#[must_use]
pub fn incarnation_start_failure(&self) -> Option<ServerError> {
self.inner
.incarnations
.as_ref()
.and_then(|authority| authority.start_failure())
}
pub(in crate::server) fn finish_store_work(&self) -> Result<(), ServerError> {
self.inner.finish_store_work()
}
pub(in crate::server) fn release_store_holders(&self) -> Result<(), ServerError> {
self.inner.runtime.services().release_store_holders()
}
}
impl SupervisorInner {
fn finish_store_work(&self) -> Result<(), ServerError> {
#[cfg(test)]
let observer = self
.reclaim_stop_observer
.lock()
.map_err(|_| refusal("reclamation stop observer lock poisoned"))?
.take();
#[cfg(test)]
if let Some(observer) = observer {
observer
.send(true)
.map_err(|error| refusal(error.to_string()))?;
}
self.held.stop();
if let Some(authority) = &self.incarnations {
authority.abandon();
}
first_failure([
self.reclaim.finish(&self.scheduler),
join_background(&self.background),
self.runtime.services().finish_background_store_work(),
])
}
}
impl Drop for SupervisorInner {
fn drop(&mut self) {
self.scheduler.shutdown();
if let Err(error) = self.finish_store_work() {
tracing::error!(%error, "connection supervisor store teardown failed");
}
}
}
fn join_background(background: &Mutex<BackgroundStart>) -> Result<(), ServerError> {
let mut start = background
.lock()
.map_err(|_| refusal("connection background join lock poisoned"))?;
start.stopped = true;
if let Some(failure) = &start.failure {
return Err(refusal(failure.clone()));
}
let joined = start.worker.take().map_or(Ok(()), |worker| {
worker
.join()
.map_err(|_| refusal("connection background store work panicked during shutdown"))
});
let started = start.incarnation.take().map_or(Ok(()), |worker| {
if worker.thread().id() == std::thread::current().id() {
return Ok(());
}
crate::server::boot_steps::stop_timed("incarnation_join", || worker.join())
.map_err(|_| refusal("the incarnation start panicked during shutdown"))
});
let mut results = vec![joined, started];
for release in &mut start.reclaim_releases {
results.push(release.finish());
}
start.reclaim_releases.clear();
let mut first = None;
for result in results {
if let Err(error) = result {
tracing::error!(%error, "background store teardown failed");
if first.is_none() {
first = Some(error);
}
}
}
if let Some(error) = first {
start.failure = Some(error.to_string());
return Err(error);
}
drop(start);
Ok(())
}
fn first_failure<const N: usize>(results: [Result<(), ServerError>; N]) -> Result<(), ServerError> {
let mut first = None;
for result in results {
if let Err(error) = result {
tracing::error!(%error, "connection store work teardown failed");
if first.is_none() {
first = Some(error);
}
}
}
first.map_or(Ok(()), Err)
}
fn refusal(message: impl Into<String>) -> ServerError {
ServerError::ListenerAccept {
message: message.into(),
}
}