use crate::{outcome::ConnectionIncarnationExhausted, wire::ConnectionIncarnation};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ConnectionIncarnationAllocatorRestore {
pub server_incarnation: u64,
pub last_examined_connection_ordinal: Option<u64>,
pub connection_ordinal_exhausted: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConnectionIncarnationAllocatorRestoreError {
OrdinalExhaustionMismatch {
last_examined_connection_ordinal: Option<u64>,
connection_ordinal_exhausted: bool,
},
}
#[derive(Debug, PartialEq, Eq)]
pub struct ConnectionIncarnationAllocator {
server_incarnation: u64,
last_examined_connection_ordinal: Option<u64>,
connection_ordinal_exhausted: bool,
}
impl ConnectionIncarnationAllocator {
pub const fn try_restore(
restored: ConnectionIncarnationAllocatorRestore,
) -> Result<Self, ConnectionIncarnationAllocatorRestoreError> {
let examined_max = matches!(restored.last_examined_connection_ordinal, Some(u64::MAX));
if examined_max != restored.connection_ordinal_exhausted {
return Err(
ConnectionIncarnationAllocatorRestoreError::OrdinalExhaustionMismatch {
last_examined_connection_ordinal: restored.last_examined_connection_ordinal,
connection_ordinal_exhausted: restored.connection_ordinal_exhausted,
},
);
}
Ok(Self {
server_incarnation: restored.server_incarnation,
last_examined_connection_ordinal: restored.last_examined_connection_ordinal,
connection_ordinal_exhausted: restored.connection_ordinal_exhausted,
})
}
#[must_use]
pub const fn server_incarnation(&self) -> u64 {
self.server_incarnation
}
#[must_use]
pub const fn last_examined_connection_ordinal(&self) -> Option<u64> {
self.last_examined_connection_ordinal
}
#[must_use]
pub const fn connection_ordinal_exhausted(&self) -> bool {
self.connection_ordinal_exhausted
}
#[must_use]
pub const fn as_restore(&self) -> ConnectionIncarnationAllocatorRestore {
ConnectionIncarnationAllocatorRestore {
server_incarnation: self.server_incarnation,
last_examined_connection_ordinal: self.last_examined_connection_ordinal,
connection_ordinal_exhausted: self.connection_ordinal_exhausted,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ServerIncarnationStartupDecision {
Fsync(ServerIncarnationFsyncIntent),
Exhausted(ServerIncarnationExhaustion),
}
#[derive(Debug, PartialEq, Eq)]
pub struct ServerIncarnationFsyncIntent {
prior_server_incarnation: u64,
resulting: ConnectionIncarnationAllocator,
}
impl ServerIncarnationFsyncIntent {
#[must_use]
pub const fn prior_server_incarnation(&self) -> u64 {
self.prior_server_incarnation
}
#[must_use]
pub const fn server_incarnation(&self) -> u64 {
self.resulting.server_incarnation
}
#[must_use]
pub const fn header_to_fsync(&self) -> ConnectionIncarnationAllocatorRestore {
self.resulting.as_restore()
}
#[must_use]
pub const fn complete_after_fsync(self) -> ConnectionIncarnationAllocator {
self.resulting
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ServerIncarnationExhaustion {
unchanged: ConnectionIncarnationAllocator,
}
impl ServerIncarnationExhaustion {
#[must_use]
pub const fn outcome(&self) -> ConnectionIncarnationExhausted {
ConnectionIncarnationExhausted::ServerIncarnation
}
#[must_use]
pub const fn into_unchanged(self) -> ConnectionIncarnationAllocator {
self.unchanged
}
}
#[must_use]
pub const fn prepare_server_incarnation_startup(
persisted: ConnectionIncarnationAllocator,
) -> ServerIncarnationStartupDecision {
let Some(server_incarnation) = persisted.server_incarnation.checked_add(1) else {
return ServerIncarnationStartupDecision::Exhausted(ServerIncarnationExhaustion {
unchanged: persisted,
});
};
ServerIncarnationStartupDecision::Fsync(ServerIncarnationFsyncIntent {
prior_server_incarnation: persisted.server_incarnation,
resulting: ConnectionIncarnationAllocator {
server_incarnation,
last_examined_connection_ordinal: None,
connection_ordinal_exhausted: false,
},
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DurableIncarnationReferencesError {
ReferenceCountExceedsBound {
actual: usize,
maximum: usize,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DurableIncarnationReferences<'a> {
values: &'a [ConnectionIncarnation],
}
impl<'a> DurableIncarnationReferences<'a> {
pub const fn try_new(
values: &'a [ConnectionIncarnation],
maximum: usize,
) -> Result<Self, DurableIncarnationReferencesError> {
if values.len() > maximum {
return Err(
DurableIncarnationReferencesError::ReferenceCountExceedsBound {
actual: values.len(),
maximum,
},
);
}
Ok(Self { values })
}
#[must_use]
pub const fn as_slice(self) -> &'a [ConnectionIncarnation] {
self.values
}
const fn collides(self, candidate: ConnectionIncarnation) -> bool {
let mut index = 0;
while index < self.values.len() {
let reference = self.values[index];
if reference.server_incarnation == candidate.server_incarnation
&& reference.connection_ordinal == candidate.connection_ordinal
{
return true;
}
index += 1;
}
false
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ConnectionIncarnationAllocationDecision {
Allocated(ConnectionIncarnationAllocation),
Exhausted(ConnectionOrdinalExhaustion),
}
#[derive(Debug, PartialEq, Eq)]
pub struct ConnectionIncarnationAllocation {
connection_incarnation: ConnectionIncarnation,
skipped_collisions: usize,
resulting: ConnectionIncarnationAllocator,
}
impl ConnectionIncarnationAllocation {
#[must_use]
pub const fn connection_incarnation(&self) -> ConnectionIncarnation {
self.connection_incarnation
}
#[must_use]
pub const fn skipped_collisions(&self) -> usize {
self.skipped_collisions
}
#[must_use]
pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
self.resulting.as_restore()
}
#[must_use]
pub const fn into_resulting(self) -> ConnectionIncarnationAllocator {
self.resulting
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ConnectionOrdinalExhaustion {
MarkExhausted(ConnectionOrdinalExhaustionCommit),
AlreadyExhausted(ConnectionOrdinalExhaustionReplay),
}
impl ConnectionOrdinalExhaustion {
#[must_use]
pub const fn outcome(&self) -> ConnectionIncarnationExhausted {
let attempted_server_incarnation = match self {
Self::MarkExhausted(commit) => commit.resulting.server_incarnation,
Self::AlreadyExhausted(replay) => replay.unchanged.server_incarnation,
};
ConnectionIncarnationExhausted::ConnectionOrdinal {
attempted_server_incarnation,
}
}
#[must_use]
pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
match self {
Self::MarkExhausted(commit) => commit.resulting.as_restore(),
Self::AlreadyExhausted(replay) => replay.unchanged.as_restore(),
}
}
#[must_use]
pub const fn into_resulting(self) -> ConnectionIncarnationAllocator {
match self {
Self::MarkExhausted(commit) => commit.resulting,
Self::AlreadyExhausted(replay) => replay.unchanged,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ConnectionOrdinalExhaustionCommit {
skipped_collisions: usize,
resulting: ConnectionIncarnationAllocator,
}
impl ConnectionOrdinalExhaustionCommit {
#[must_use]
pub const fn skipped_collisions(&self) -> usize {
self.skipped_collisions
}
#[must_use]
pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
self.resulting.as_restore()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ConnectionOrdinalExhaustionReplay {
unchanged: ConnectionIncarnationAllocator,
}
impl ConnectionOrdinalExhaustionReplay {
#[must_use]
pub const fn unchanged_header(&self) -> ConnectionIncarnationAllocatorRestore {
self.unchanged.as_restore()
}
}
#[must_use]
pub fn allocate_connection_incarnation(
allocator: ConnectionIncarnationAllocator,
references: DurableIncarnationReferences<'_>,
) -> ConnectionIncarnationAllocationDecision {
if allocator.connection_ordinal_exhausted {
return ConnectionIncarnationAllocationDecision::Exhausted(
ConnectionOrdinalExhaustion::AlreadyExhausted(ConnectionOrdinalExhaustionReplay {
unchanged: allocator,
}),
);
}
let next_candidate = allocator
.last_examined_connection_ordinal
.map_or(Some(0), |last| last.checked_add(1));
let Some(mut candidate_ordinal) = next_candidate else {
return ConnectionIncarnationAllocationDecision::Exhausted(
ConnectionOrdinalExhaustion::AlreadyExhausted(ConnectionOrdinalExhaustionReplay {
unchanged: allocator,
}),
);
};
let mut skipped_collisions = 0;
loop {
let candidate = ConnectionIncarnation::new(allocator.server_incarnation, candidate_ordinal);
if !references.collides(candidate) {
let connection_ordinal_exhausted = candidate_ordinal == u64::MAX;
return ConnectionIncarnationAllocationDecision::Allocated(
ConnectionIncarnationAllocation {
connection_incarnation: candidate,
skipped_collisions,
resulting: ConnectionIncarnationAllocator {
server_incarnation: allocator.server_incarnation,
last_examined_connection_ordinal: Some(candidate_ordinal),
connection_ordinal_exhausted,
},
},
);
}
skipped_collisions += 1;
if candidate_ordinal == u64::MAX {
return ConnectionIncarnationAllocationDecision::Exhausted(
ConnectionOrdinalExhaustion::MarkExhausted(ConnectionOrdinalExhaustionCommit {
skipped_collisions,
resulting: ConnectionIncarnationAllocator {
server_incarnation: allocator.server_incarnation,
last_examined_connection_ordinal: Some(u64::MAX),
connection_ordinal_exhausted: true,
},
}),
);
}
candidate_ordinal += 1;
}
}