Async
The asyncio surface.
:class:AsyncSession is the same participant as :class:paxodin.Session with the
driving done for you: a receiver task delivers frames, a ticker task keeps the
engine's timers moving, and await session.append(...) resolves when the
command is released. There is no polling loop to remember.
Ownership is explicit because the engine allows exactly one transition at a
time. Every transition -- yours or the driver's -- runs under one
:class:asyncio.Lock, and journal syncs run in a worker thread so an fsync
never stalls the event loop. Cancellation is honest: cancelling append stops
waiting, it does not un-propose. Paxos has no cancel, and this API will not
pretend otherwise.
AsyncTransport
Bases: Protocol
Moves opaque frames between peers, asynchronously.
The adapter owns framing, connections and -- critically -- authentication: a sender id inside a frame is a claim, not proof. It must preserve frame boundaries and bound the bytes it queues.
send
async
send(*, peer: NodeId, frame: bytes) -> None
Deliver one frame to one peer, best effort.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
peer
|
NodeId
|
The recipient's identity. |
required |
frame
|
bytes
|
The encoded envelope. |
required |
receive
async
receive() -> tuple[NodeId, bytes]
Wait for the next frame.
Returns:
| Type | Description |
|---|---|
tuple[NodeId, bytes]
|
The authenticated sender and the frame. |
close
async
close() -> None
Release the transport's resources.
AsyncSession
One participant, driven by asyncio.
Example
from paxodin.testing import AsyncCluster async def main() -> int: ... async with AsyncCluster(3) as cluster: ... receipt = await cluster.append(b"set counter 41") ... return receipt.slot import asyncio asyncio.run(main()) 1
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.
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 released here, in order. Zero if none.
start
async
start() -> None
Start the receiver and ticker tasks. Idempotent.
close
async
close() -> None
Stop driving, then close the node, journal, transport and history.
Closing stops local work. It does not withdraw a proposal that peers may still choose.
campaign
async
campaign() -> None
Stand for leadership now, without waiting for a timeout.
append
async
append(value: bytes, *, timeout: float = 5.0) -> Receipt
Submit a command and wait until it is durably released here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
bytes
|
The command bytes, at most |
required |
timeout
|
float
|
Seconds to wait for the decision. |
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; nothing is forwarded. |
ValueTooLarge
|
The command exceeds the profile. Nothing was admitted. |
ProposalLost
|
A different value was decided in the admitted slot. |
CommitTimeout
|
The wait ended. Paxos was not cancelled and the value
may still be chosen; it is also a |
CancelledError
|
The wait was cancelled. The proposal stands: cancelling a coroutine cannot un-propose a value. |
Note
Success means agreement and release at this participant. It says nothing about peers having received it, and never that the application applied it.
state
state() -> NodeState
Return a snapshot of this participant.
entries
entries(
start: Slot = 1, *, limit: int | None = None
) -> Iterator[Committed]
Iterate released entries from start. See :meth:paxodin.Session.entries.