Skip to content

Session

The durable participant: one order, no mechanism.

:class:Session runs the sequence the core's contract requires -- persist the writes, confirm them, release the decisions, send the messages, serve the catch-up requests, finish the batch -- and nothing else. Every byte it moves goes through an adapter the caller supplied, so this module contains no socket, no filesystem assumption beyond the journal protocol, and no retry policy.

It reads a clock for exactly one reason: the core counts its election and heartbeat timeouts in logical ticks and deliberately leaves their duration to the host. tick_interval is where that decision is made, once, visibly.

Session

One participant, driven through a caller-supplied journal and transport.

A session represents this member. The other members must also be running; nothing here starts them, forwards to them, or discovers them.

Example

from paxodin import Session from paxodin.storage import FileHistory, FileJournal with Session( # doctest: +SKIP ... node_id=1, ... members=[1, 2, 3], ... configuration_id=1, ... journal=FileJournal("state/node-1"), ... history=FileHistory("state/node-1"), ... transport=transport, ... ) as session: ... receipt = session.append(b"set counter 41", timeout=5.0) ... print(receipt.slot)

Note

There is no background thread. Between appends, call :meth:poll to drive incoming traffic, ticks and retransmission.

node property

node: Node

The underlying participant, for hosts that need direct control.

node_id property

node_id: NodeId

This participant's identity.

is_leader property

is_leader: bool

Whether this participant currently believes it leads.

A belief, not a lease: the engine has no lease, so this can be stale the moment it is read. It says who will try to sequence a command.

leader property

leader: NodeId | None

The member this participant thinks leads, if it has heard from one.

last_committed property

last_committed: Slot

The last slot this participant has released, in order. Zero if none.

state

state() -> NodeState

Return a snapshot of this participant.

Returns:

Type Description
NodeState

Its role, ballot, released prefix and seal state.

Note

This is what this member knows. The core has no lease, so it is never a statement about the cluster right now.

discharge

discharge(batch: PendingBatch) -> None

Run the durability order for one batch through this session's adapters.

Persist, confirm, release, send, serve, finish. The order is the whole point: a message that left before its record was durable can be reverted by a crash, and two different values can then be chosen for one slot.

This is public so a host that drives :attr:node directly -- to batch proposals, or to sequence its own transitions -- can still hand each batch to the session for the part it must not get wrong.

Parameters:

Name Type Description Default
batch PendingBatch

A pending batch produced by :attr:node.

required

Raises:

Type Description
StorageError

If the journal could not persist the records. The batch is left unconfirmed and the node blocked; reopen and replay.

poll

poll(*, timeout: float = 0.0) -> int

Drive incoming traffic, ticks and retransmission.

There is no background worker, so a participant only makes progress while someone calls this or :meth:append.

Parameters:

Name Type Description Default
timeout float

Seconds to wait for traffic, on the monotonic clock. Zero processes everything already queued and returns without waiting.

0.0

Returns:

Type Description
int

How many frames were processed.

Raises:

Type Description
ValueError

If timeout is negative or not finite.

campaign

campaign() -> None

Stand for leadership immediately, without waiting for a timeout.

append

append(value: bytes, *, timeout: float = 5.0) -> Receipt

Submit a command and drive progress until it is durably released.

Parameters:

Name Type Description Default
value bytes

The command bytes, at most profile.max_value_bytes. An empty command is legal and stays distinct from a no-op.

required
timeout float

Seconds to wait, on a monotonic clock.

5.0

Returns:

Type Description
Receipt

A receipt naming the configuration and slot that hold the command.

Raises:

Type Description
NotLeader

This participant is a follower. No forwarding is performed; route the request to the leader yourself.

ValueTooLarge

The command exceeds the profile. Nothing was admitted.

ProposalLost

A different value was decided in the slot this command was admitted to. In single-leader mode there is no resubmission, so a leader that loses its ballot mid-flight drops the command.

CommitTimeout

The wait ended. This did not cancel anything: the value may still be chosen. Keep polling and inspect local history; retry only behind an application-level command id.

ValueError

If timeout is negative or not finite.

Note

Success means agreement and release at this participant. It does not mean every peer received the command, and it never means the application applied it. Exactly-once application is an application protocol, not something a receipt can establish.

entries

entries(
    start: Slot = 1, *, limit: int | None = None
) -> Iterator[Committed]

Iterate this participant's released entries from start, in order.

This reports what this member knows; it is not a freshness guarantee and may lag the cluster arbitrarily.

Parameters:

Name Type Description Default
start Slot

The first slot to yield.

1
limit int | None

Stop after this many, or run to the end of the released prefix.

None

Yields:

Type Description
Committed

Entries in slot order, each an owned object.

Example

from paxodin.testing import Cluster with Cluster(3) as cluster: ... _ = cluster.append(b"a") ... _ = cluster.append(b"b") ... [e.entry.body for e in cluster.session(1).entries()] == [b"a", b"b"] True

committed_since

committed_since(
    slot: Slot, *, limit: int = 128
) -> list[Committed]

Read up to limit released entries from slot. See :meth:entries.

Parameters:

Name Type Description Default
slot Slot

The first slot wanted.

required
limit int

The most entries to return.

128

Returns:

Type Description
list[Committed]

The entries, in slot order.

close

close() -> None

Close the node, the journal, the transport and the history.

Closing stops local work. It does not withdraw a proposal that peers may still choose.