liminal_protocol/lifecycle/
incarnation.rs1use crate::{outcome::ConnectionIncarnationExhausted, wire::ConnectionIncarnation};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct ConnectionIncarnationAllocatorRestore {
18 pub server_incarnation: u64,
20 pub last_examined_connection_ordinal: Option<u64>,
22 pub connection_ordinal_exhausted: bool,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum ConnectionIncarnationAllocatorRestoreError {
29 OrdinalExhaustionMismatch {
31 last_examined_connection_ordinal: Option<u64>,
33 connection_ordinal_exhausted: bool,
35 },
36}
37
38#[derive(Debug, PartialEq, Eq)]
43pub struct ConnectionIncarnationAllocator {
44 server_incarnation: u64,
45 last_examined_connection_ordinal: Option<u64>,
46 connection_ordinal_exhausted: bool,
47}
48
49impl ConnectionIncarnationAllocator {
50 pub const fn try_restore(
57 restored: ConnectionIncarnationAllocatorRestore,
58 ) -> Result<Self, ConnectionIncarnationAllocatorRestoreError> {
59 let examined_max = matches!(restored.last_examined_connection_ordinal, Some(u64::MAX));
60 if examined_max != restored.connection_ordinal_exhausted {
61 return Err(
62 ConnectionIncarnationAllocatorRestoreError::OrdinalExhaustionMismatch {
63 last_examined_connection_ordinal: restored.last_examined_connection_ordinal,
64 connection_ordinal_exhausted: restored.connection_ordinal_exhausted,
65 },
66 );
67 }
68
69 Ok(Self {
70 server_incarnation: restored.server_incarnation,
71 last_examined_connection_ordinal: restored.last_examined_connection_ordinal,
72 connection_ordinal_exhausted: restored.connection_ordinal_exhausted,
73 })
74 }
75
76 #[must_use]
78 pub const fn server_incarnation(&self) -> u64 {
79 self.server_incarnation
80 }
81
82 #[must_use]
84 pub const fn last_examined_connection_ordinal(&self) -> Option<u64> {
85 self.last_examined_connection_ordinal
86 }
87
88 #[must_use]
90 pub const fn connection_ordinal_exhausted(&self) -> bool {
91 self.connection_ordinal_exhausted
92 }
93
94 #[must_use]
96 pub const fn as_restore(&self) -> ConnectionIncarnationAllocatorRestore {
97 ConnectionIncarnationAllocatorRestore {
98 server_incarnation: self.server_incarnation,
99 last_examined_connection_ordinal: self.last_examined_connection_ordinal,
100 connection_ordinal_exhausted: self.connection_ordinal_exhausted,
101 }
102 }
103}
104
105#[derive(Debug, PartialEq, Eq)]
107pub enum ServerIncarnationStartupDecision {
108 Fsync(ServerIncarnationFsyncIntent),
110 Exhausted(ServerIncarnationExhaustion),
112}
113
114#[derive(Debug, PartialEq, Eq)]
116pub struct ServerIncarnationFsyncIntent {
117 prior_server_incarnation: u64,
118 resulting: ConnectionIncarnationAllocator,
119}
120
121impl ServerIncarnationFsyncIntent {
122 #[must_use]
124 pub const fn prior_server_incarnation(&self) -> u64 {
125 self.prior_server_incarnation
126 }
127
128 #[must_use]
130 pub const fn server_incarnation(&self) -> u64 {
131 self.resulting.server_incarnation
132 }
133
134 #[must_use]
136 pub const fn header_to_fsync(&self) -> ConnectionIncarnationAllocatorRestore {
137 self.resulting.as_restore()
138 }
139
140 #[must_use]
143 pub const fn complete_after_fsync(self) -> ConnectionIncarnationAllocator {
144 self.resulting
145 }
146}
147
148#[derive(Debug, PartialEq, Eq)]
150pub struct ServerIncarnationExhaustion {
151 unchanged: ConnectionIncarnationAllocator,
152}
153
154impl ServerIncarnationExhaustion {
155 #[must_use]
157 pub const fn outcome(&self) -> ConnectionIncarnationExhausted {
158 ConnectionIncarnationExhausted::ServerIncarnation
159 }
160
161 #[must_use]
163 pub const fn into_unchanged(self) -> ConnectionIncarnationAllocator {
164 self.unchanged
165 }
166}
167
168#[must_use]
174pub const fn prepare_server_incarnation_startup(
175 persisted: ConnectionIncarnationAllocator,
176) -> ServerIncarnationStartupDecision {
177 let Some(server_incarnation) = persisted.server_incarnation.checked_add(1) else {
178 return ServerIncarnationStartupDecision::Exhausted(ServerIncarnationExhaustion {
179 unchanged: persisted,
180 });
181 };
182
183 ServerIncarnationStartupDecision::Fsync(ServerIncarnationFsyncIntent {
184 prior_server_incarnation: persisted.server_incarnation,
185 resulting: ConnectionIncarnationAllocator {
186 server_incarnation,
187 last_examined_connection_ordinal: None,
188 connection_ordinal_exhausted: false,
189 },
190 })
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195pub enum DurableIncarnationReferencesError {
196 ReferenceCountExceedsBound {
198 actual: usize,
200 maximum: usize,
202 },
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
207pub struct DurableIncarnationReferences<'a> {
208 values: &'a [ConnectionIncarnation],
209}
210
211impl<'a> DurableIncarnationReferences<'a> {
212 pub const fn try_new(
222 values: &'a [ConnectionIncarnation],
223 maximum: usize,
224 ) -> Result<Self, DurableIncarnationReferencesError> {
225 if values.len() > maximum {
226 return Err(
227 DurableIncarnationReferencesError::ReferenceCountExceedsBound {
228 actual: values.len(),
229 maximum,
230 },
231 );
232 }
233 Ok(Self { values })
234 }
235
236 #[must_use]
238 pub const fn as_slice(self) -> &'a [ConnectionIncarnation] {
239 self.values
240 }
241
242 const fn collides(self, candidate: ConnectionIncarnation) -> bool {
243 let mut index = 0;
244 while index < self.values.len() {
245 let reference = self.values[index];
246 if reference.server_incarnation == candidate.server_incarnation
247 && reference.connection_ordinal == candidate.connection_ordinal
248 {
249 return true;
250 }
251 index += 1;
252 }
253 false
254 }
255}
256
257#[derive(Debug, PartialEq, Eq)]
259pub enum ConnectionIncarnationAllocationDecision {
260 Allocated(ConnectionIncarnationAllocation),
262 Exhausted(ConnectionOrdinalExhaustion),
264}
265
266#[derive(Debug, PartialEq, Eq)]
268pub struct ConnectionIncarnationAllocation {
269 connection_incarnation: ConnectionIncarnation,
270 skipped_collisions: usize,
271 resulting: ConnectionIncarnationAllocator,
272}
273
274impl ConnectionIncarnationAllocation {
275 #[must_use]
277 pub const fn connection_incarnation(&self) -> ConnectionIncarnation {
278 self.connection_incarnation
279 }
280
281 #[must_use]
283 pub const fn skipped_collisions(&self) -> usize {
284 self.skipped_collisions
285 }
286
287 #[must_use]
289 pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
290 self.resulting.as_restore()
291 }
292
293 #[must_use]
295 pub const fn into_resulting(self) -> ConnectionIncarnationAllocator {
296 self.resulting
297 }
298}
299
300#[derive(Debug, PartialEq, Eq)]
302pub enum ConnectionOrdinalExhaustion {
303 MarkExhausted(ConnectionOrdinalExhaustionCommit),
305 AlreadyExhausted(ConnectionOrdinalExhaustionReplay),
307}
308
309impl ConnectionOrdinalExhaustion {
310 #[must_use]
312 pub const fn outcome(&self) -> ConnectionIncarnationExhausted {
313 let attempted_server_incarnation = match self {
314 Self::MarkExhausted(commit) => commit.resulting.server_incarnation,
315 Self::AlreadyExhausted(replay) => replay.unchanged.server_incarnation,
316 };
317 ConnectionIncarnationExhausted::ConnectionOrdinal {
318 attempted_server_incarnation,
319 }
320 }
321
322 #[must_use]
324 pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
325 match self {
326 Self::MarkExhausted(commit) => commit.resulting.as_restore(),
327 Self::AlreadyExhausted(replay) => replay.unchanged.as_restore(),
328 }
329 }
330
331 #[must_use]
333 pub const fn into_resulting(self) -> ConnectionIncarnationAllocator {
334 match self {
335 Self::MarkExhausted(commit) => commit.resulting,
336 Self::AlreadyExhausted(replay) => replay.unchanged,
337 }
338 }
339}
340
341#[derive(Debug, PartialEq, Eq)]
343pub struct ConnectionOrdinalExhaustionCommit {
344 skipped_collisions: usize,
345 resulting: ConnectionIncarnationAllocator,
346}
347
348impl ConnectionOrdinalExhaustionCommit {
349 #[must_use]
351 pub const fn skipped_collisions(&self) -> usize {
352 self.skipped_collisions
353 }
354
355 #[must_use]
357 pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
358 self.resulting.as_restore()
359 }
360}
361
362#[derive(Debug, PartialEq, Eq)]
364pub struct ConnectionOrdinalExhaustionReplay {
365 unchanged: ConnectionIncarnationAllocator,
366}
367
368impl ConnectionOrdinalExhaustionReplay {
369 #[must_use]
371 pub const fn unchanged_header(&self) -> ConnectionIncarnationAllocatorRestore {
372 self.unchanged.as_restore()
373 }
374}
375
376#[must_use]
382pub fn allocate_connection_incarnation(
383 allocator: ConnectionIncarnationAllocator,
384 references: DurableIncarnationReferences<'_>,
385) -> ConnectionIncarnationAllocationDecision {
386 if allocator.connection_ordinal_exhausted {
387 return ConnectionIncarnationAllocationDecision::Exhausted(
388 ConnectionOrdinalExhaustion::AlreadyExhausted(ConnectionOrdinalExhaustionReplay {
389 unchanged: allocator,
390 }),
391 );
392 }
393
394 let next_candidate = allocator
395 .last_examined_connection_ordinal
396 .map_or(Some(0), |last| last.checked_add(1));
397 let Some(mut candidate_ordinal) = next_candidate else {
398 return ConnectionIncarnationAllocationDecision::Exhausted(
399 ConnectionOrdinalExhaustion::AlreadyExhausted(ConnectionOrdinalExhaustionReplay {
400 unchanged: allocator,
401 }),
402 );
403 };
404 let mut skipped_collisions = 0;
405
406 loop {
407 let candidate = ConnectionIncarnation::new(allocator.server_incarnation, candidate_ordinal);
408 if !references.collides(candidate) {
409 let connection_ordinal_exhausted = candidate_ordinal == u64::MAX;
410 return ConnectionIncarnationAllocationDecision::Allocated(
411 ConnectionIncarnationAllocation {
412 connection_incarnation: candidate,
413 skipped_collisions,
414 resulting: ConnectionIncarnationAllocator {
415 server_incarnation: allocator.server_incarnation,
416 last_examined_connection_ordinal: Some(candidate_ordinal),
417 connection_ordinal_exhausted,
418 },
419 },
420 );
421 }
422
423 skipped_collisions += 1;
424 if candidate_ordinal == u64::MAX {
425 return ConnectionIncarnationAllocationDecision::Exhausted(
426 ConnectionOrdinalExhaustion::MarkExhausted(ConnectionOrdinalExhaustionCommit {
427 skipped_collisions,
428 resulting: ConnectionIncarnationAllocator {
429 server_incarnation: allocator.server_incarnation,
430 last_examined_connection_ordinal: Some(u64::MAX),
431 connection_ordinal_exhausted: true,
432 },
433 }),
434 );
435 }
436 candidate_ordinal += 1;
437 }
438}