use std::sync::{Arc, Mutex};
use liminal::durability::{DurableStore, bridge::block_on};
use liminal_protocol::{outcome::ConnectionIncarnationExhausted, wire::ConnectionIncarnation};
use crate::ServerError;
use crate::server::participant::incarnation_stream::{
IncarnationAllocation, IncarnationStartup, IncarnationStream, StartedIncarnationStream,
};
#[derive(Debug)]
pub(super) struct ConnectionIncarnationAuthority {
state: Mutex<ConnectionIncarnationAuthorityState>,
}
#[derive(Debug)]
enum ConnectionIncarnationAuthorityState {
Ready(StartedIncarnationStream),
ConnectionOrdinalExhausted { attempted_server_incarnation: u64 },
Failed,
}
impl ConnectionIncarnationAuthority {
#[cfg(test)]
pub(super) const fn from_started_for_test(stream: StartedIncarnationStream) -> Self {
Self {
state: Mutex::new(ConnectionIncarnationAuthorityState::Ready(stream)),
}
}
pub(super) fn startup(
store: Arc<dyn DurableStore>,
maximum_references: usize,
) -> Result<Self, ServerError> {
let startup = block_on(IncarnationStream::new(store, maximum_references).startup())
.map_err(|error| ServerError::ParticipantIncarnation {
phase: "server startup bridge",
message: error.to_string(),
})?
.map_err(|error| ServerError::ParticipantIncarnation {
phase: "server startup persistence",
message: error.to_string(),
})?;
match startup {
IncarnationStartup::Started(stream) => Ok(Self {
state: Mutex::new(ConnectionIncarnationAuthorityState::Ready(stream)),
}),
IncarnationStartup::Exhausted(ConnectionIncarnationExhausted::ServerIncarnation) => {
Err(ServerError::ServerIncarnationExhausted)
}
IncarnationStartup::Exhausted(ConnectionIncarnationExhausted::ConnectionOrdinal {
attempted_server_incarnation,
}) => Err(ServerError::ParticipantIncarnation {
phase: "server startup protocol",
message: format!(
"unexpected connection-ordinal exhaustion for server incarnation \
{attempted_server_incarnation} during startup"
),
}),
}
}
pub(super) fn allocate(
&self,
referenced_incarnations: &[ConnectionIncarnation],
) -> Result<ConnectionIncarnation, ServerError> {
let mut state = self
.state
.lock()
.map_err(|error| ServerError::ParticipantIncarnation {
phase: "connection allocation lock",
message: error.to_string(),
})?;
let current = core::mem::replace(&mut *state, ConnectionIncarnationAuthorityState::Failed);
let result = match current {
ConnectionIncarnationAuthorityState::Ready(mut stream) => {
match block_on(stream.allocate(referenced_incarnations)) {
Err(error) => Err(ServerError::ParticipantIncarnation {
phase: "connection allocation bridge",
message: error.to_string(),
}),
Ok(Err(error)) => Err(ServerError::ParticipantIncarnation {
phase: "connection allocation persistence",
message: error.to_string(),
}),
Ok(Ok(IncarnationAllocation::Allocated {
connection_incarnation,
skipped_collisions: _,
})) => {
*state = ConnectionIncarnationAuthorityState::Ready(stream);
Ok(connection_incarnation)
}
Ok(Ok(IncarnationAllocation::Exhausted(
ConnectionIncarnationExhausted::ConnectionOrdinal {
attempted_server_incarnation,
},
))) => {
*state =
ConnectionIncarnationAuthorityState::ConnectionOrdinalExhausted {
attempted_server_incarnation,
};
Err(ServerError::ConnectionIncarnationExhausted {
attempted_server_incarnation,
})
}
Ok(Ok(IncarnationAllocation::Exhausted(
ConnectionIncarnationExhausted::ServerIncarnation,
))) => Err(ServerError::ServerIncarnationExhausted),
}
}
ConnectionIncarnationAuthorityState::ConnectionOrdinalExhausted {
attempted_server_incarnation,
} => {
*state = ConnectionIncarnationAuthorityState::ConnectionOrdinalExhausted {
attempted_server_incarnation,
};
Err(ServerError::ConnectionIncarnationExhausted {
attempted_server_incarnation,
})
}
ConnectionIncarnationAuthorityState::Failed => {
Err(ServerError::ParticipantIncarnation {
phase: "connection allocation unavailable",
message: "a prior allocation had an ambiguous durable result; process recovery is required"
.to_owned(),
})
}
};
drop(state);
result
}
}