Node
The low-level participant: one transition at a time, no I/O.
Node is the Python face of the Odin effect machine. It performs no I/O, owns
no thread and reads no clock. A transition mutates the node and leaves a
:class:PendingBatch describing what the host must now do -- persist these
records, send these messages, release these entries -- in that order.
Most applications want :class:paxodin.Session, which runs that order for them.
Reach for Node when the host needs to own scheduling or batching itself.
AbandonedWritesWarning
Bases: UserWarning
A node was closed while it still held unconfirmed writes.
Those records were handed out but never acknowledged durable, so the node must be rebuilt by journal replay rather than resumed.
PendingBatch
One retained effects batch: a durability obligation, not a result.
The order is fixed and the type enforces it::
writes -> append and sync -> persisted() -> messages / committed
-> requests -> finish()
Every probe and copy is idempotent, so a failed allocation can be retried without rerunning the transition that produced it.
Note
status is the protocol outcome and is independent of the effects. A
transition can report an error and leave writes the host must still
persist, so checking the status is never a substitute for discharging
the batch.
status
property
status: int
The protocol status of the transition, independent of its effects.
assigned_slot
property
assigned_slot: Slot
The slot a proposal was admitted to, or zero.
requires_barrier
property
requires_barrier: bool
True when the batch holds a promise or a vote.
A decision or a trim record is derived state a host may persist behind a cheaper barrier; a promise or a vote is the indelible ink whose loss lets a crash choose two values for one slot.
phase
property
phase: BatchPhase
Where the batch has got to, so an interrupted caller can resume.
refresh
refresh() -> None
Re-read the batch report from the node.
writes
writes() -> list[WriteRecord]
Return the durable records, in journal order.
Returns:
| Name | Type | Description |
|---|---|---|
list[WriteRecord]
|
Every record of this transition. Append them in order and sync before |
|
calling |
list[WriteRecord]
|
meth: |
persisted
persisted() -> None
Declare every record from :meth:writes appended and synced.
This is the only call that unblocks the outputs, because transmitting a reply or releasing a decision before its record is on stable storage is exactly what lets a crash choose two values for one slot.
Raises:
| Type | Description |
|---|---|
WritesNotCopied
|
If :meth: |
StaleToken
|
If a later transition superseded this batch. |
messages
messages() -> list[Envelope]
Return the outbound envelopes.
Returns:
| Type | Description |
|---|---|
list[Envelope]
|
Owned envelopes, each stamped with its configuration. |
Raises:
| Type | Description |
|---|---|
WritesUnconfirmed
|
If the writes are not yet confirmed durable. |
committed
committed() -> list[Committed]
Return the entries released by this transition, in slot order.
Returns:
| Type | Description |
|---|---|
list[Committed]
|
Owned entries, contiguous with everything released before them. |
Raises:
| Type | Description |
|---|---|
WritesUnconfirmed
|
If the writes are not yet confirmed durable. |
requests
requests() -> list[ServeRange]
Return history ranges peers asked for below this node's memory floor.
Serving one means transmitting commits from retained history, which is a send like any other, so it waits on the same confirmation.
Returns:
| Type | Description |
|---|---|
list[ServeRange]
|
Owned request records. |
Raises:
| Type | Description |
|---|---|
WritesUnconfirmed
|
If the writes are not yet confirmed durable. |
assigned_slots
assigned_slots() -> list[Slot]
Return the slots a batch proposal was admitted to.
Returns:
| Type | Description |
|---|---|
list[Slot]
|
One slot per submitted value, in submission order. |
finish
finish() -> None
Release the batch so the next transition may run.
Raises:
| Type | Description |
|---|---|
WritesUnconfirmed
|
If the batch still holds unconfirmed writes. Releasing here would discard records the journal never took. |
Node
One local participant, driven one transition at a time.
A node owns an opaque native handle. Exactly one call may be inside that
handle at a time: a second thread blocks, and the same thread re-entering
raises :class:ReentrantCall rather than deadlocking. Every value a node
returns is an owned Python object whose lifetime is independent of the
native window.
Example
node = Node(node_id=1, members=[1, 2, 3], configuration_id=1) with node.campaign() as batch: ... records = batch.writes() # append and sync these ... batch.persisted() ... outbound = batch.messages() node.close()
profile
property
profile: Profile
The capacities this build was compiled with.
closed
property
closed: bool
True once the native handle has been released.
restore
classmethod
restore(
*,
node_id: NodeId,
members: Sequence[NodeId],
configuration_id: int,
records: Sequence[WriteRecord],
floor: Slot = 0,
**options: Unpack[NodeOptions],
) -> Node
Rebuild a participant from its journal.
The records are folded with the lifetime fold, which tolerates the out-of-order promises a journal legitimately accumulates across restarts. The ledger never crosses the ABI: the bridge owns it for the duration of the replay and hands the core a finished one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_id
|
NodeId
|
This member's identity. |
required |
members
|
Sequence[NodeId]
|
Every voting member of the configuration. |
required |
configuration_id
|
int
|
The configuration this journal belongs to. |
required |
records
|
Sequence[WriteRecord]
|
Every durable record, in append order. |
required |
floor
|
Slot
|
The last slot the host durably consumed. Cells at or below it that hold only an open vote are cleared. |
0
|
**options
|
Unpack[NodeOptions]
|
The same tuning arguments :meth: |
{}
|
Returns:
| Type | Description |
|---|---|
Node
|
The restored node. |
Raises:
| Type | Description |
|---|---|
PaxodinError
|
If a record contradicts the ledger, which means the journal is corrupt or was replayed out of order. |
state
state() -> NodeState
Return a snapshot of this participant.
Returns:
| Type | Description |
|---|---|
NodeState
|
The role, ballot, leader, released prefix, memory floor and seal |
NodeState
|
state, gathered in one call so they cannot disagree with each other. |
Note
decided_through is this participant's released prefix. It is not
a freshness guarantee: the core has no lease, so a local read can be
arbitrarily stale.
campaign
campaign() -> PendingBatch
Start a campaign for leadership.
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. |
tick
tick() -> PendingBatch
Advance the logical clock by one tick.
A tick is a logical unit, not a duration: the core counts election and
heartbeat timeouts in ticks and leaves their real-time meaning to the
host. :class:paxodin.Session binds it to a wall clock.
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. |
propose
propose(value: bytes) -> PendingBatch
Submit one command.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
bytes
|
The command bytes, at most |
required |
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. Its |
PendingBatch
|
slot the command was admitted to when the status is success. |
Raises:
| Type | Description |
|---|---|
ValueTooLarge
|
If the command exceeds the profile. No proposal was admitted and the node is unchanged. |
propose_batch
propose_batch(values: Sequence[bytes]) -> PendingBatch
Submit several commands, admitted whole or not at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Sequence[bytes]
|
Between one and |
required |
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. Read the slots with |
PendingBatch
|
|
Raises:
| Type | Description |
|---|---|
ValueTooLarge
|
If any command exceeds the profile. |
InvalidArgument
|
If the count is zero or above |
step
step(envelope: Envelope) -> PendingBatch
Deliver one received message.
The configuration stamp is checked before the core sees anything: a mismatch resets the batch and reports it, with no writes, no messages and no state change. Stale traffic is refused, never relabelled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope
|
Envelope
|
The decoded message, as the caller's codec produced it. |
required |
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. |
reconnected
reconnected(peer: NodeId) -> PendingBatch
Tell the node a peer is reachable again.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
peer
|
NodeId
|
The peer's identity. |
required |
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. |
request_catch_up
request_catch_up(
peer: NodeId, from_slot: Slot
) -> PendingBatch
Ask a peer for decided history from from_slot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
peer
|
NodeId
|
The peer to ask. |
required |
from_slot
|
Slot
|
The first slot wanted. |
required |
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. |
install_chosen_trim
install_chosen_trim(
trim_id: int, chosen_trim_slot: Slot
) -> PendingBatch
Adopt a trim anchor certified by the host.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trim_id
|
int
|
The host's identity for the state image. |
required |
chosen_trim_slot
|
Slot
|
The slot the image folds in, inclusive. |
required |
Returns:
| Type | Description |
|---|---|
PendingBatch
|
The batch this transition produced. |
advance_memory_floor
advance_memory_floor(through: Slot) -> None
Record that released entries through through are durably consumed.
This frees window cells for reuse, which is why it refuses while a batch is live: the batch's released entries still point into those cells, and losing them would silently break a contiguously released prefix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
through
|
Slot
|
The last durably consumed slot. |
required |
Raises:
| Type | Description |
|---|---|
BatchPending
|
If a batch has not been finished. |
set_campaign_enabled
set_campaign_enabled(enabled: bool) -> None
Allow or forbid this participant from starting elections.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether campaigning is permitted. |
required |
decided_span
decided_span(from_slot: Slot) -> int
Return how many decided entries sit at or above from_slot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_slot
|
Slot
|
The first slot of interest. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The count, so a caller can size a buffer before reading. |
Raises:
| Type | Description |
|---|---|
Trimmed
|
If |
read_decided
read_decided(
from_slot: Slot, limit: int
) -> list[Committed]
Read a bounded window of the decided prefix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_slot
|
Slot
|
The first slot to read. |
required |
limit
|
int
|
The most entries to return. |
required |
Returns:
| Type | Description |
|---|---|
list[Committed]
|
Up to |
Raises:
| Type | Description |
|---|---|
Trimmed
|
If |
close
close() -> None
Release the native handle. Calling this again does nothing.
Closing while a batch still holds unconfirmed writes abandons them and warns: those records were handed out but never acknowledged durable, so the node must be rebuilt by journal replay rather than resumed.
Raises:
| Type | Description |
|---|---|
ReentrantCall
|
If called from inside another call on this handle. |