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,
impl<Step, Index> KernelTransaction<Step, Index>where
Step: TransitionStep,
Index: RecordIndex,
Sourcepub fn new(defaults: ConfigDefaults, index: Index) -> Self
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).
Sourcepub fn prepare<F>(
&mut self,
envelope: &WireEnvelope,
plan: F,
) -> RecordPreparation<Step>
pub fn prepare<F>( &mut self, envelope: &WireEnvelope, plan: F, ) -> RecordPreparation<Step>
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.
Sourcepub fn commit(
&mut self,
token: &PrepareToken,
appended_head: &Digest,
) -> Result<CommittedTransition<Step>, KernelFault>
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.
Sourcepub fn abort(
&mut self,
token: &PrepareToken,
) -> Result<KernelRecord, KernelFault>
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.
Sourcepub fn note_append_conflict(
&mut self,
token: &PrepareToken,
observed_head: Option<&Digest>,
) -> KernelFault
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.
Sourcepub fn rebuild_from_records<F>(
records: &[KernelRecord],
defaults: ConfigDefaults,
index: Index,
plan: F,
) -> Result<Self, KernelFault>
pub fn rebuild_from_records<F>( records: &[KernelRecord], defaults: ConfigDefaults, index: Index, plan: F, ) -> Result<Self, 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.
Sourcepub fn replay_committed<F>(
&mut self,
input: &NormalizedInput,
expected_record_digest: &Digest,
plan: &mut F,
) -> Result<KernelRecord, KernelFault>
pub fn replay_committed<F>( &mut self, input: &NormalizedInput, expected_record_digest: &Digest, plan: &mut F, ) -> Result<KernelRecord, 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.
Sourcepub fn restore_from_checkpoint(
checkpoint: &KernelCheckpoint,
defaults: ConfigDefaults,
index: Index,
) -> Result<Self, KernelFault>
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.
Sourcepub fn checkpoint_boundary(&self) -> Option<CheckpointBoundary>
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.
Sourcepub fn tail_inputs(&self) -> Vec<CanonicalInput>
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.
Sourcepub fn checkpoint_candidate(
&self,
projection: LogicalStateProjection,
) -> Result<CheckpointCandidate, KernelFault>
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.
Sourcepub fn checkpoint_rebase(
&self,
base: &CheckpointBoundary,
base_state: LogicalKernelState,
) -> Result<CheckpointCandidate, KernelFault>
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.
Sourcepub fn transition_state_for_restore(
&self,
root_kind: Option<RootKind>,
focus: Option<ExecutionFocus>,
) -> Result<TransitionState, KernelFault>
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.
Sourcepub fn note_checkpoint_acked(
&mut self,
boundary: &CheckpointBoundary,
) -> Result<TailUsage, KernelFault>
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.
pub fn operation_id(&self) -> Option<&OperationId>
pub fn config(&self) -> Option<&ResolvedOperationConfig>
pub fn head(&self) -> Option<DurableHead>
pub fn lifecycle(&self) -> OperationLifecycle
pub fn terminal(&self) -> Option<&KernelTerminal>
Sourcepub fn pending_effects(&self) -> impl Iterator<Item = &KernelEffect>
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.
Sourcepub fn pending_effects_in_order(&self) -> Vec<&KernelEffect>
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.
pub fn is_effect_resolved(&self, effect_id: &EffectId) -> bool
pub fn knows_launch_token(&self, token: &LaunchToken) -> bool
pub fn committed_step(&self, input_id: &InputId) -> Option<&Step>
pub fn outstanding_token(&self) -> Option<&PrepareToken>
pub fn has_candidate(&self) -> bool
Sourcepub fn poison(&self) -> Option<&KernelFault>
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).
pub fn is_poisoned(&self) -> bool
pub fn bounds(&self) -> TailBounds
pub fn index(&self) -> &Index
pub fn tail_usage(&self) -> TailUsage
pub fn tail_pressure(&self) -> TailPressure
Trait Implementations§
Source§impl<Step: Clone, Index: Clone> Clone for KernelTransaction<Step, Index>
impl<Step: Clone, Index: Clone> Clone for KernelTransaction<Step, Index>
Source§fn clone(&self) -> KernelTransaction<Step, Index>
fn clone(&self) -> KernelTransaction<Step, Index>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more