portunus_machine_aux (portunus v0.14.0)

View Source

Pure decision logic for portunus_machine's aux renewal and expiry sweep. Aux state is per-server, in-memory and never replicated. Renewal writes nothing to the Raft log. Only expiry becomes a logged {expire_leases, ...} command.

The rules match the etcd lessor:

  • every lease's operative deadline lives here, on the leader, in monotonic time. Renewal moves it forward without touching the log
  • a lease known to the machine but missing from deadlines is seeded at its full TTL. A new leader, a restarted server and a fresh grant all err toward late expiry, never early
  • an expiry proposal carries the lease's refreshed index as a fence. While the machine still holds the lease at that exact index, the proposal is live: renewals answer lease_expired and the sweep does not re-propose. Anything that changes refreshed or removes the lease voids the entry
  • a term change means another leader renewed these holders in between, so both maps are cleared first

Every function takes the applied leases as a lease_view() map (#{lease_id() => {ttl_ms, refreshed}}), the current Raft term and a caller-supplied monotonic now.

This means that the decisions can be tested without a Ra cluster.

portunus_machine:handle_aux/5 extracts the inputs and turns the outputs into effects.

Summary

Functions

As the name suggests, this function is called on every leader tick.

A non-leader holds no deadlines. Clears both maps.

Called once a grant_lease command has been committed, whether it created the lease or was a repeat grant by the same owner.

Renews each lease the machine still holds that has no live expiry proposal.

Types

aux()

-opaque aux()

expire_pair()

-type expire_pair() :: {portunus:lease_id(), ra:index()}.

lease_view()

-type lease_view() :: #{portunus:lease_id() => {pos_integer(), ra:index()}}.

Functions

leader_tick(Aux0, Leases, Term, Now)

-spec leader_tick(aux(), lease_view(), non_neg_integer(), integer()) -> {aux(), [expire_pair()]}.

As the name suggests, this function is called on every leader tick.

Reconciles the term, drops entries for leases the machine no longer holds, seeds untracked leases at their full TTL, then proposes expiry for every deadline at or past Now that doesn't already have a live proposal.

Returns the pairs to append as one {expire_leases, ...} command. Pairs are sorted so that tests observe a stable order.

new()

-spec new() -> aux().

non_leader_tick/1

-spec non_leader_tick(aux()) -> aux().

A non-leader holds no deadlines. Clears both maps.

refreshed(Aux0, Leases, Term, Now, LeaseIds)

-spec refreshed(aux(), lease_view(), non_neg_integer(), integer(), [portunus:lease_id()]) -> aux().

Called once a grant_lease command has been committed, whether it created the lease or was a repeat grant by the same owner.

Resets each granted lease's aux deadline to a full TTL (Now + Ttl) so that a lease whose old deadline had already passed is not removed as expired right after its grant succeeded.

renew(Aux0, Leases, Term, Now, LeaseIds)

-spec renew(aux(), lease_view(), non_neg_integer(), integer(), [portunus:lease_id()]) ->
               {aux(), [{portunus:lease_id(), ok | {error, lease_expired}}]}.

Renews each lease the machine still holds that has no live expiry proposal.

A lease with a live proposal answers lease_expired even though the command has not applied yet. The appended command may still expire it, so acknowledging the renewal would be wrong.