Skip to main content

KernelTransaction

Struct KernelTransaction 

Source
pub struct KernelTransaction<Step, Index> { /* private fields */ }
Expand description

The prepare/commit/abort state machine of §8.2.

One operation, one transaction, one candidate slot. Everything durable lives in the journal behind RecordIndex; what is held here is the fold of that journal that the next transition needs — head, lifecycle, pending effects, launch tokens and the ephemeral steps the records only carry a digest of.

Implementations§

Source§

impl<Step, Index> KernelTransaction<Step, Index>
where Step: TransitionStep, Index: RecordIndex,

Source

pub fn new(defaults: ConfigDefaults, index: Index) -> Self

A transaction with no journal yet.

It starts on the bootstrap tail bound — defaults.baseline.recovery_policy.tail_bounds — because the genesis append has to be bounded by something and the operation’s own configuration is not resolved until that very record. The moment genesis commits, the frozen value replaces it (§5e-5).

Source

pub fn prepare<F>( &mut self, envelope: &WireEnvelope, plan: F, ) -> RecordPreparation<Step>
where F: FnOnce(&PlanContext<'_>) -> Result<Step, KernelFault>,

Normalise, validate and plan one input (§8.2 lines 3–4).

Returns the closed three-arm result of §7.13. Nothing in this call touches the journal: a Prepared result means a record was built, and it becomes durable only when the host appends it and calls Self::commit.

Every path that does not return Prepared leaves this transaction byte-for-byte unchanged, including the ones that ran the planner.

Source

pub fn commit( &mut self, token: &PrepareToken, appended_head: &Digest, ) -> Result<CommittedTransition<Step>, KernelFault>

Commit the candidate the host has already appended (§8.2 lines 5–6).

appended_head is the journal’s head after the CAS append; it must be this candidate’s own record digest. There is no separate “the append succeeded” call, and that is the point: between the append and this call the kernel holds no state that could be rolled back, so §8.3’s “append succeeded, commit failed ⇒ discard the runtime and rebuild” is the only expressible outcome. Any failure here poisons the transaction — a poisoned transaction refuses every later call and must be replaced via Self::rebuild_from_records.

Source

pub fn abort( &mut self, token: &PrepareToken, ) -> Result<KernelRecord, KernelFault>

Discard a candidate the host has not appended (§8.3 line 3).

The only legal abort window. It is not reachable after a successful append because Self::commit consumes the candidate, so “we appended, then something threw, so we aborted” cannot be written against this API.

Source

pub fn note_append_conflict( &mut self, token: &PrepareToken, observed_head: Option<&Digest>, ) -> KernelFault

The host’s CAS append failed its precondition (§8.3 line 4).

Discards the candidate — legal, because a failed CAS wrote nothing — and then fails closed: the journal moved under this runtime, so every later call is refused until the host runs the rebuild/retry loop. This layer never rebuilds itself; deciding to re-read the head and replay the input is the host-side closure of Task 7b.

Source

pub fn rebuild_from_records<F>( records: &[KernelRecord], defaults: ConfigDefaults, index: Index, plan: F, ) -> Result<Self, KernelFault>
where F: FnMut(&PlanContext<'_>) -> Result<Step, KernelFault>,

Rebuild a transaction from a journal’s records (§8.3 lines 5–6, §12.2).

Every record is re-planned through plan and the resulting record is rebuilt and compared with the stored one, so a rebuild proves three things at once: the chain links up, the planner is still deterministic, and the step digest the journal froze is the step this binary produces. Anything else is KernelFaultCode::RecordCorrupted — fail closed rather than resume on a history this binary cannot reproduce.

Source

pub fn replay_committed<F>( &mut self, input: &NormalizedInput, expected_record_digest: &Digest, plan: &mut F, ) -> Result<KernelRecord, KernelFault>
where F: FnMut(&PlanContext<'_>) -> Result<Step, KernelFault>,

Replay one already-committed transition onto this runtime (§8.3 lines 5–6, §12.2 lines 4–7).

The replay primitive: rebuild_from_records is a loop over it, and so is §12.2’s tail replay. That is what makes “there is no second resume state machine” a structural fact rather than a claim — a checkpoint’s tail_inputs and a journal’s records reach the fold through the same function, differing only in where the expected digest came from.

It deliberately does not go through prepare. A record that is already durable is not a new decision: prepare’s first move is to ask the index whether this input_id was already accepted, and during a replay the honest answer is “yes, by the very record we are replaying” — which would turn the fold into a Replayed and quietly skip it. Re-planning and comparing digests is the stronger check anyway: it proves the chain links up, the planner is still deterministic, and the step digest the journal froze is the step this binary produces.

Source

pub fn restore_from_checkpoint( checkpoint: &KernelCheckpoint, defaults: ConfigDefaults, index: Index, ) -> Result<Self, KernelFault>

Rebuild a transaction from a checkpoint’s logical state (§12.2 line 3).

This is the transaction half of the §12.2 ladder; the driver half is CanonicalOperationDriver::restore_logical_state and the two are composed by restore_operation, which is also what verifies the result. Nothing is replayed here: the returned transaction sits at base_step_seq, and the caller replays the bounded tail and then the post-checkpoint records onto it through the ordinary prepare/commit fold.

The cost is O(1) in the length of the run — that is the whole of §12’s claim. What makes it sound is that everything a transaction decides with is in the checkpoint: the frozen configuration, the effect ledger, the replay ledger, the cancellation and the terminal.

Source

pub fn checkpoint_boundary(&self) -> Option<CheckpointBoundary>

The prefix a checkpoint candidate taken right now would cover.

Independent of the transaction candidate slot in both directions (§22.14): taking this does not require an empty slot, and an outstanding candidate does not move the boundary.

Source

pub fn tail_inputs(&self) -> Vec<CanonicalInput>

The canonical inputs the tail still carries, in step order (§12.1 tail_inputs).

Exactly the range (last acked checkpoint, head]. A checkpoint whose base_step_seq sits further back needs the host to have kept the older logical state — that is the rebase half of §12.3, which Task 16 owns.

Source

pub fn checkpoint_candidate( &self, projection: LogicalStateProjection, ) -> Result<CheckpointCandidate, KernelFault>

Assemble a checkpoint candidate over the current durable head (§12.3, first half).

Generation only. It installs nothing, acks nothing and reclaims nothing — and it leaves the transaction untouched, which is what makes §12.3 rule 1 (“appends may continue after a candidate”) true by construction rather than by discipline: &self, no candidate slot, no tail mutation.

The candidate is a full-state checkpoint: base_step_seq == through_step_seq, so its bounded tail is empty and a restore needs no replay before the post-checkpoint records. Self::checkpoint_rebase produces the incremental form.

Source

pub fn checkpoint_rebase( &self, base: &CheckpointBoundary, base_state: LogicalKernelState, ) -> Result<CheckpointCandidate, KernelFault>

The rebase form of §12.3 rule 11: an older logical state plus the canonical inputs that carry it forward to the current head.

base is a checkpoint boundary this runtime already produced — in practice the last one the host installed. The tail is Self::tail_inputs restricted to (base, head], taken from the transaction’s own accounting rather than re-harvested from the journal, so a rebase can be built after the prefix it rebases onto has been reclaimed.

Why it exists at all: a full-state candidate re-serialises the whole logical state every time, and a long run with a big context pays that cost per checkpoint. A rebase pays it once and then appends bounded tails. The contract that makes the two interchangeable is that they produce the same state_digest for the same logical state — the header and the tail move, the state does not.

Source

pub fn transition_state_for_restore( &self, root_kind: Option<RootKind>, focus: Option<ExecutionFocus>, ) -> Result<TransitionState, KernelFault>

§12.2 · the transition partition, for a restore’s own re-projection.

Public because the restore has to be able to ask “what does this runtime say its transition state is” without going through checkpoint_candidate, which would build a whole checkpoint header it is about to throw away.

Source

pub fn note_checkpoint_acked( &mut self, boundary: &CheckpointBoundary, ) -> Result<TailUsage, KernelFault>

Reclaim the tail prefix an installed checkpoint covers — only after the host acked it (§12.3 rule 6).

The boundary is verified against the tail’s own digests, so a checkpoint from another operation, or one claiming a step this journal never had, is refused. It does not have to name the current head (§12.3 rule 2): records appended after the candidate stay as tail.

What it reclaims is the tail accounting, never the replay/dedup ledgers — §12.3 rule 7 is explicit that an ack must not empty the window that makes a redelivery idempotent.

Source

pub fn operation_id(&self) -> Option<&OperationId>

Source

pub fn config(&self) -> Option<&ResolvedOperationConfig>

Source

pub fn head(&self) -> Option<DurableHead>

Source

pub fn lifecycle(&self) -> OperationLifecycle

Source

pub fn terminal(&self) -> Option<&KernelTerminal>

Source

pub fn pending_effects(&self) -> impl Iterator<Item = &KernelEffect>

Effects published by committed records and not yet resolved. A prepared-but-uncommitted step’s effects are not here (§15.2).

Iteration order is the map’s lexicographic key order, which is not the numeric publication order — step:10 sorts before step:9. Consumers that pick “the first pending effect” as the host’s next action want Self::pending_effects_in_order.

Source

pub fn pending_effects_in_order(&self) -> Vec<&KernelEffect>

The same effects in publication order — earlier steps first, and within one step the mint order that puts a syscall batch’s own effects ahead of the continuation’s. This is the order a host consumes a multi-effect step in: take the head, resolve it, re-derive.

Source

pub fn is_effect_resolved(&self, effect_id: &EffectId) -> bool

Source

pub fn knows_launch_token(&self, token: &LaunchToken) -> bool

Source

pub fn committed_step(&self, input_id: &InputId) -> Option<&Step>

Source

pub fn outstanding_token(&self) -> Option<&PrepareToken>

Source

pub fn has_candidate(&self) -> bool

Source

pub fn poison(&self) -> Option<&KernelFault>

The fault that poisoned this transaction, if any. A poisoned transaction is not recoverable in place — the host discards it and rebuilds from the journal (§8.3).

Source

pub fn is_poisoned(&self) -> bool

Source

pub fn bounds(&self) -> TailBounds

Source

pub fn index(&self) -> &Index

Source

pub fn tail_usage(&self) -> TailUsage

Source

pub fn tail_pressure(&self) -> TailPressure

Trait Implementations§

Source§

impl<Step: Clone, Index: Clone> Clone for KernelTransaction<Step, Index>

Source§

fn clone(&self) -> KernelTransaction<Step, Index>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<Step: Debug, Index: Debug> Debug for KernelTransaction<Step, Index>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<Step: PartialEq, Index: PartialEq> PartialEq for KernelTransaction<Step, Index>

Source§

fn eq(&self, other: &KernelTransaction<Step, Index>) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<Step: PartialEq, Index: PartialEq> StructuralPartialEq for KernelTransaction<Step, Index>

Auto Trait Implementations§

§

impl<Step, Index> Freeze for KernelTransaction<Step, Index>
where Index: Freeze, Option<Candidate<Step>>: Freeze, BTreeMap<InputId, Step>: Freeze,

§

impl<Step, Index> RefUnwindSafe for KernelTransaction<Step, Index>
where Index: RefUnwindSafe, Option<Candidate<Step>>: RefUnwindSafe, BTreeMap<InputId, Step>: RefUnwindSafe,

§

impl<Step, Index> Send for KernelTransaction<Step, Index>
where Index: Send, Option<Candidate<Step>>: Send, BTreeMap<InputId, Step>: Send,

§

impl<Step, Index> Sync for KernelTransaction<Step, Index>
where Index: Sync, Option<Candidate<Step>>: Sync, BTreeMap<InputId, Step>: Sync,

§

impl<Step, Index> Unpin for KernelTransaction<Step, Index>
where Index: Unpin, Option<Candidate<Step>>: Unpin, BTreeMap<InputId, Step>: Unpin,

§

impl<Step, Index> UnsafeUnpin for KernelTransaction<Step, Index>
where Index: UnsafeUnpin, Option<Candidate<Step>>: UnsafeUnpin, BTreeMap<InputId, Step>: UnsafeUnpin,

§

impl<Step, Index> UnwindSafe for KernelTransaction<Step, Index>
where Index: UnwindSafe, Option<Candidate<Step>>: UnwindSafe, BTreeMap<InputId, Step>: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.