use std::sync::Arc;
use liminal::durability::DurableStore;
use super::{ConnectionIncarnationAuthority, ConnectionIncarnationAuthorityState as State};
use crate::ServerError;
use crate::health::AdmissionReadiness;
use crate::server::participant::ParticipantSemanticHandler;
pub(in crate::server::connection) const NOT_STARTED_PHASE: &str = "incarnation start pending";
#[derive(Debug)]
pub(in crate::server::connection) struct IncarnationStartInputs {
pub(in crate::server::connection) store: Arc<dyn DurableStore>,
pub(in crate::server::connection) maximum_references: usize,
pub(in crate::server::connection) maximum_conversations: u64,
pub(in crate::server::connection) handler: Arc<dyn ParticipantSemanticHandler>,
}
#[derive(Debug)]
pub(in crate::server::connection) enum StartFailure {
Incarnation {
phase: &'static str,
message: String,
},
ServerIncarnationExhausted,
Other(String),
}
impl StartFailure {
fn of(error: &ServerError) -> Self {
match error {
ServerError::ParticipantIncarnation { phase, message } => Self::Incarnation {
phase,
message: message.clone(),
},
ServerError::ServerIncarnationExhausted => Self::ServerIncarnationExhausted,
other => Self::Other(other.to_string()),
}
}
pub(in crate::server::connection) fn error(&self) -> ServerError {
match self {
Self::Incarnation { phase, message } => ServerError::ParticipantIncarnation {
phase,
message: message.clone(),
},
Self::ServerIncarnationExhausted => ServerError::ServerIncarnationExhausted,
Self::Other(message) => ServerError::ParticipantIncarnation {
phase: "server startup",
message: message.clone(),
},
}
}
}
impl ConnectionIncarnationAuthority {
pub(in crate::server::connection) fn starting(
inputs: IncarnationStartInputs,
admission: AdmissionReadiness,
) -> Result<Self, ServerError> {
let maximum_conversations = super::conversation_bound(inputs.maximum_conversations)?;
admission.set_available(false);
Ok(Self::with_state(
State::NotStarted(Box::new(inputs)),
maximum_conversations,
admission,
))
}
pub(in crate::server::connection) fn ensure_started(&self) -> Result<(), ServerError> {
match self.claim()? {
Claim::Run(inputs) => self.start(&inputs),
Claim::Done(result) => result,
}
}
fn claim(&self) -> Result<Claim, ServerError> {
let mut state = self
.started
.wait_while(
Self::lock(&self.state, "incarnation start lock")?,
|state| {
let starting = matches!(state, State::Starting);
#[cfg(test)]
if starting {
self.note_waiting();
}
starting
},
)
.map_err(|error| ServerError::ParticipantIncarnation {
phase: "incarnation start wait",
message: error.to_string(),
})?;
let claim = match std::mem::replace(&mut *state, State::Starting) {
State::NotStarted(inputs) => Claim::Run(inputs),
State::StartFailed(failure) => {
let error = failure.error();
*state = State::StartFailed(failure);
Claim::Done(Err(error))
}
started => {
*state = started;
Claim::Done(Ok(()))
}
};
drop(state);
Ok(claim)
}
pub(in crate::server::connection) fn start_failure(&self) -> Option<ServerError> {
match &*self.state.lock().ok()? {
State::StartFailed(failure) => Some(failure.error()),
_ => None,
}
}
fn start(&self, inputs: &IncarnationStartInputs) -> Result<(), ServerError> {
let mut outcome = Outcome {
authority: self,
published: false,
};
let started = Self::start_stream(
Arc::clone(&inputs.store),
inputs.maximum_references,
inputs.handler.as_ref(),
);
outcome.publish(started)
}
#[cfg(test)]
fn note_waiting(&self) {
if let Some(waiting) = self.on_wait.lock().ok().and_then(|slot| slot.clone()) {
let _sent = waiting.send(());
}
}
#[cfg(test)]
pub(in crate::server::connection) fn on_wait_for_test(
&self,
waiting: std::sync::mpsc::Sender<()>,
) {
if let Ok(mut slot) = self.on_wait.lock() {
*slot = Some(waiting);
}
}
}
enum Claim {
Run(Box<IncarnationStartInputs>),
Done(Result<(), ServerError>),
}
struct Outcome<'authority> {
authority: &'authority ConnectionIncarnationAuthority,
published: bool,
}
impl Outcome<'_> {
fn publish(
&mut self,
started: Result<super::StartedIncarnationStream, ServerError>,
) -> Result<(), ServerError> {
let (next, result) = match started {
Ok(stream) => (State::Ready(stream), Ok(())),
Err(error) => (State::StartFailed(StartFailure::of(&error)), Err(error)),
};
let ready = matches!(next, State::Ready(_));
self.set(next);
if ready {
self.authority.admission.set_available(true);
}
result
}
fn set(&mut self, next: State) {
self.published = true;
match self.authority.state.lock() {
Ok(mut state) => *state = next,
Err(poisoned) => *poisoned.into_inner() = next,
}
self.authority.started.notify_all();
}
}
impl Drop for Outcome<'_> {
fn drop(&mut self) {
if !self.published {
tracing::error!(
"the incarnation start unwound before it finished; admission is refused"
);
self.set(State::StartFailed(StartFailure::Other(
"the incarnation start unwound before it finished".to_owned(),
)));
}
}
}