portunus_machine_aux (portunus v0.11.0)
View SourceThe pure decision core behind portunus_machine's aux renewal and expiry
sweep. Aux state is per-server and in-memory, never replicated, so lease
renewal writes nothing to the Raft log; only expiry, an actual state
change, becomes a logged {expire_leases, ...} command.
The rules, the same ones the etcd lessor uses:
- the operative deadline of every lease lives here, on the leader, in monotonic time; renewal moves it forward without touching the log
- a lease known to the machine but absent from
deadlinesis seeded at its full TTL, so a new leader, a restarted server, and a fresh grant all err toward late expiry, never early - an expiry proposal carries the lease's
refreshedindex as a fence; while the machine still holds the lease with that exact index the proposal is live, renewals for the lease answerlease_expired, and the sweep does not re-propose it. Anything that changesrefreshed(or removes the lease) voids the entry - a term change means another leader renewed these holders in between, so both maps are cleared before anything else
Every function takes the applied leases as a view
(#{lease_id() => {ttl_ms, refreshed}}), the current Raft term, and a
caller-supplied monotonic now, so the decisions are testable without a
Ra cluster; portunus_machine:handle_aux/5 extracts the inputs and turns
the outputs into effects.
Summary
Functions
The leader sweep: reconcile the term, drop entries for leases the machine
no longer holds and void pending entries, seed leases not yet tracked at
their full TTL, then propose expiry for every deadline at or past Now
that has no live proposal. Returns the expire pairs to append as one
{expire_leases, ...} command; pairs are sorted so tests see a stable
order.
A non-leader holds no operative deadlines: clear both maps.
A grant committed (initial or an idempotent re-grant): extend the aux deadlines to the full TTL, so a re-granted lease whose old deadline had passed is not proposed for expiry right after a successful grant.
Renew each lease the machine still holds and that has no live expiry
proposal. A lease with a live proposal answers lease_expired (the
standard possible-loss answer) even though the command has not applied
yet: the appended command may still expire it, so acknowledging the
renewal would be wrong.
Types
-opaque aux()
-type expire_pair() :: {portunus:lease_id(), ra:index()}.
-type lease_view() :: #{portunus:lease_id() => {pos_integer(), ra:index()}}.
Functions
-spec leader_tick(aux(), lease_view(), non_neg_integer(), integer()) -> {aux(), [expire_pair()]}.
The leader sweep: reconcile the term, drop entries for leases the machine
no longer holds and void pending entries, seed leases not yet tracked at
their full TTL, then propose expiry for every deadline at or past Now
that has no live proposal. Returns the expire pairs to append as one
{expire_leases, ...} command; pairs are sorted so tests see a stable
order.
-spec new() -> aux().
A non-leader holds no operative deadlines: clear both maps.
-spec refreshed(aux(), lease_view(), non_neg_integer(), integer(), [portunus:lease_id()]) -> aux().
A grant committed (initial or an idempotent re-grant): extend the aux deadlines to the full TTL, so a re-granted lease whose old deadline had passed is not proposed for expiry right after a successful grant.
-spec renew(aux(), lease_view(), non_neg_integer(), integer(), [portunus:lease_id()]) -> {aux(), [{portunus:lease_id(), ok | {error, lease_expired}}]}.
Renew each lease the machine still holds and that has no live expiry
proposal. A lease with a live proposal answers lease_expired (the
standard possible-loss answer) even though the command has not applied
yet: the appended command may still expire it, so acknowledging the
renewal would be wrong.