Skip to content

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 profile.max_value_bytes.

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 TimeoutError.

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.