Skip to main content

liminal_protocol/lifecycle/
incarnation.rs

1//! Monotonic connection-incarnation allocation.
2//!
3//! Frozen `PARTICIPANT-CONTRACT.md` lines 484-504 require a checked, fsynced
4//! startup increment of `server_incarnation`, followed by checked connection
5//! ordinals which never wrap, rebase, or reuse a value. Allocation checks every
6//! supplied live or durable reference before publication. The supplied
7//! reference collection is explicitly bounded so collision retry is finite.
8//!
9//! The transition results own their resulting header. A server persists that
10//! header in the same transaction which publishes the allocated incarnation;
11//! replay from the same raw pre-state and reference set is deterministic.
12
13use crate::{outcome::ConnectionIncarnationExhausted, wire::ConnectionIncarnation};
14
15/// Raw durable connection-incarnation allocator header.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct ConnectionIncarnationAllocatorRestore {
18    /// Server incarnation persisted by the most recent startup fsync.
19    pub server_incarnation: u64,
20    /// Last ordinal allocated or rejected because a durable reference owned it.
21    pub last_examined_connection_ordinal: Option<u64>,
22    /// Fixed header bit which permanently closes this incarnation's ordinal space.
23    pub connection_ordinal_exhausted: bool,
24}
25
26/// Invalid durable connection-incarnation allocator header.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum ConnectionIncarnationAllocatorRestoreError {
29    /// The exhaustion bit and terminal examined ordinal disagree.
30    OrdinalExhaustionMismatch {
31        /// Restored last examined ordinal.
32        last_examined_connection_ordinal: Option<u64>,
33        /// Restored fixed exhaustion bit.
34        connection_ordinal_exhausted: bool,
35    },
36}
37
38/// Validated monotonic connection-incarnation allocator state.
39///
40/// This state is intentionally not `Copy` or `Clone`: each transition consumes
41/// its pre-state and returns the one resulting state which must be committed.
42#[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    /// Restores the durable fixed header after validating the terminal bit.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`ConnectionIncarnationAllocatorRestoreError`] unless ordinal
55    /// `u64::MAX` and the exhaustion bit are either both present or both absent.
56    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    /// Returns the current durable server incarnation.
77    #[must_use]
78    pub const fn server_incarnation(&self) -> u64 {
79        self.server_incarnation
80    }
81
82    /// Returns the last allocated or collision-rejected connection ordinal.
83    #[must_use]
84    pub const fn last_examined_connection_ordinal(&self) -> Option<u64> {
85        self.last_examined_connection_ordinal
86    }
87
88    /// Returns whether this server incarnation can never mint another ordinal.
89    #[must_use]
90    pub const fn connection_ordinal_exhausted(&self) -> bool {
91        self.connection_ordinal_exhausted
92    }
93
94    /// Projects the exact fixed header which a binding must persist atomically.
95    #[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/// Startup decision for the checked server-incarnation increment.
106#[derive(Debug, PartialEq, Eq)]
107pub enum ServerIncarnationStartupDecision {
108    /// Persist and fsync the checked increment before participant mode starts.
109    Fsync(ServerIncarnationFsyncIntent),
110    /// The persisted server counter is already terminal and remains unchanged.
111    Exhausted(ServerIncarnationExhaustion),
112}
113
114/// Checked startup write which must be fsynced before participant mode starts.
115#[derive(Debug, PartialEq, Eq)]
116pub struct ServerIncarnationFsyncIntent {
117    prior_server_incarnation: u64,
118    resulting: ConnectionIncarnationAllocator,
119}
120
121impl ServerIncarnationFsyncIntent {
122    /// Returns the durable counter value from before this startup.
123    #[must_use]
124    pub const fn prior_server_incarnation(&self) -> u64 {
125        self.prior_server_incarnation
126    }
127
128    /// Returns the checked server incarnation to persist and fsync.
129    #[must_use]
130    pub const fn server_incarnation(&self) -> u64 {
131        self.resulting.server_incarnation
132    }
133
134    /// Returns the exact fresh-ordinal header to persist and fsync.
135    #[must_use]
136    pub const fn header_to_fsync(&self) -> ConnectionIncarnationAllocatorRestore {
137        self.resulting.as_restore()
138    }
139
140    /// Releases the current allocator after the server has persisted and fsynced
141    /// [`Self::header_to_fsync`] in its startup transaction.
142    #[must_use]
143    pub const fn complete_after_fsync(self) -> ConnectionIncarnationAllocator {
144        self.resulting
145    }
146}
147
148/// State-preserving terminal server-incarnation refusal.
149#[derive(Debug, PartialEq, Eq)]
150pub struct ServerIncarnationExhaustion {
151    unchanged: ConnectionIncarnationAllocator,
152}
153
154impl ServerIncarnationExhaustion {
155    /// Returns the exact R-D1 exhaustion payload.
156    #[must_use]
157    pub const fn outcome(&self) -> ConnectionIncarnationExhausted {
158        ConnectionIncarnationExhausted::ServerIncarnation
159    }
160
161    /// Returns the unchanged terminal durable state.
162    #[must_use]
163    pub const fn into_unchanged(self) -> ConnectionIncarnationAllocator {
164        self.unchanged
165    }
166}
167
168/// Produces the checked startup fsync intent or exact server exhaustion.
169///
170/// The successful intent resets the ordinal namespace only because it owns a
171/// strictly newer server incarnation. No startup path wraps or reuses a server
172/// value.
173#[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/// Error constructing a bounded complete durable-reference collection.
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195pub enum DurableIncarnationReferencesError {
196    /// The supplied complete reference collection exceeds its configured bound.
197    ReferenceCountExceedsBound {
198        /// Number of supplied live and durable references.
199        actual: usize,
200        /// Maximum reference count proved by server storage configuration.
201        maximum: usize,
202    },
203}
204
205/// Complete bounded live and durable connection-incarnation references.
206#[derive(Clone, Copy, Debug, PartialEq, Eq)]
207pub struct DurableIncarnationReferences<'a> {
208    values: &'a [ConnectionIncarnation],
209}
210
211impl<'a> DurableIncarnationReferences<'a> {
212    /// Validates the complete reference collection against its storage bound.
213    ///
214    /// Duplicates are legal because one incarnation can be named by a binding,
215    /// receipt, work item, and recovery row simultaneously.
216    ///
217    /// # Errors
218    ///
219    /// Returns [`DurableIncarnationReferencesError`] if `values` exceeds the
220    /// configured maximum reference count.
221    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    /// Returns the supplied complete reference collection.
237    #[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/// Connection-ordinal allocation decision.
258#[derive(Debug, PartialEq, Eq)]
259pub enum ConnectionIncarnationAllocationDecision {
260    /// One unique incarnation is ready for atomic header persistence/publication.
261    Allocated(ConnectionIncarnationAllocation),
262    /// No ordinal remains for this server incarnation.
263    Exhausted(ConnectionOrdinalExhaustion),
264}
265
266/// Successful connection-incarnation allocation and resulting durable state.
267#[derive(Debug, PartialEq, Eq)]
268pub struct ConnectionIncarnationAllocation {
269    connection_incarnation: ConnectionIncarnation,
270    skipped_collisions: usize,
271    resulting: ConnectionIncarnationAllocator,
272}
273
274impl ConnectionIncarnationAllocation {
275    /// Returns the unique pair to publish only with the resulting header commit.
276    #[must_use]
277    pub const fn connection_incarnation(&self) -> ConnectionIncarnation {
278        self.connection_incarnation
279    }
280
281    /// Returns how many referenced candidates were skipped before publication.
282    #[must_use]
283    pub const fn skipped_collisions(&self) -> usize {
284        self.skipped_collisions
285    }
286
287    /// Returns the exact resulting fixed header.
288    #[must_use]
289    pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
290        self.resulting.as_restore()
291    }
292
293    /// Consumes the commit and returns the resulting allocator state.
294    #[must_use]
295    pub const fn into_resulting(self) -> ConnectionIncarnationAllocator {
296        self.resulting
297    }
298}
299
300/// Exact ordinal-exhaustion transition.
301#[derive(Debug, PartialEq, Eq)]
302pub enum ConnectionOrdinalExhaustion {
303    /// A referenced `u64::MAX` candidate atomically sets the terminal bit.
304    MarkExhausted(ConnectionOrdinalExhaustionCommit),
305    /// The terminal bit was already durable, so the refusal changes no state.
306    AlreadyExhausted(ConnectionOrdinalExhaustionReplay),
307}
308
309impl ConnectionOrdinalExhaustion {
310    /// Returns the exact R-D1 ordinal exhaustion payload.
311    #[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    /// Returns the exact resulting fixed header for commit or unchanged replay.
323    #[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    /// Consumes the transition and returns its resulting allocator state.
332    #[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/// Atomic fixed-header update after a referenced terminal collision.
342#[derive(Debug, PartialEq, Eq)]
343pub struct ConnectionOrdinalExhaustionCommit {
344    skipped_collisions: usize,
345    resulting: ConnectionIncarnationAllocator,
346}
347
348impl ConnectionOrdinalExhaustionCommit {
349    /// Returns how many referenced candidates, including MAX, were skipped.
350    #[must_use]
351    pub const fn skipped_collisions(&self) -> usize {
352        self.skipped_collisions
353    }
354
355    /// Returns the exact fixed header which atomically sets exhaustion.
356    #[must_use]
357    pub const fn resulting_header(&self) -> ConnectionIncarnationAllocatorRestore {
358        self.resulting.as_restore()
359    }
360}
361
362/// Idempotent refusal from an already terminal connection-ordinal header.
363#[derive(Debug, PartialEq, Eq)]
364pub struct ConnectionOrdinalExhaustionReplay {
365    unchanged: ConnectionIncarnationAllocator,
366}
367
368impl ConnectionOrdinalExhaustionReplay {
369    /// Returns the exact unchanged terminal header.
370    #[must_use]
371    pub const fn unchanged_header(&self) -> ConnectionIncarnationAllocatorRestore {
372        self.unchanged.as_restore()
373    }
374}
375
376/// Allocates one checked connection ordinal, skipping every exact collision.
377///
378/// A successful MAX allocation and a referenced MAX collision both atomically
379/// set `connection_ordinal_exhausted`. A subsequent call has no candidate and
380/// returns the stable [`ConnectionOrdinalExhaustion::AlreadyExhausted`] arm.
381#[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}