portunus_machine (portunus v0.14.0)

View Source

The portunus Ra state machine. It manages leases, locks, fencing tokens, and a score-ordered succession queue (FIFO among equal scores).

Key decisions:

  1. apply/3 never reads node-local time, only the leader-stamped system_time in the command metadata.
  2. Never uses make_ref/0 or self/0. Instead it derives tokens and IDs from the Raft log index, packed with a per-incarnation epoch (think restarts; see token_info/1).

The main goal of having per-incarnation epochs is to make sure that higher epochs result in higher fencing tokens being produced.

Lease renewal and expiry timing live off the Raft log, in per-server aux state (see portunus_machine_aux): renewals arrive over ra:consistent_aux/3 and move an in-memory deadline on the leader, and the leader's aux tick proposes {expire_leases, ...} commands for leases whose deadline passed. Replicated state keeps, per lease, only the refreshed index of the last logged command that refreshed it (its grant, initial or idempotent); an expiry proposal is fenced with that index, so a proposal outrun by a re-grant is skipped by apply/3. Steady-state renewal therefore appends nothing and triggers no fsync(2) on any member. The trade-off, the same one etcd makes: a leader change can extend a lease by up to one full TTL (late expiry only, never early).

Summary

Functions

Decompose an epoch-packed identifier (a fencing token, an auto-assigned lease id, or a watch reference) for logging and debugging. An epoch of 0 means the identifier was minted before the incarnation had a stamp.

Types

command()

-type command() ::
          {grant_lease, portunus:option(lease_id()), pos_integer(), owner(), portunus:option(pid())} |
          {revoke_lease, lease_id()} |
          {acquire, lease_id(), lock_key(), owner(), term(), wait | nowait} |
          {acquire, lease_id(), lock_key(), owner(), term(), wait | nowait, integer()} |
          {release, lock_key(), token()} |
          {transfer, lock_key(), token(), owner()} |
          {transfer_batch, [{lock_key(), token(), owner()}]} |
          {leave_queue, lock_key(), lease_id()} |
          {watch, lock_key(), pid()} |
          {unwatch, watch_ref()} |
          {expire_leases, [portunus_machine_aux:expire_pair()]} |
          {down, pid(), term()} |
          {nodeup | nodedown, node()} |
          {machine_version, ra_machine:version(), ra_machine:version()}.

lease_id()

-type lease_id() :: term().

lock_key()

-type lock_key() :: term().

owner()

-type owner() :: term().

owner_info()

-type owner_info() :: #{owner := owner(), lease := lease_id(), token := token(), context := term()}.

state()

-opaque state()

token()

-type token() :: non_neg_integer().

watch_ref()

-type watch_ref() :: non_neg_integer().

Functions

apply(Meta, Cmd, State0)

handle_aux/5

init(Config)

-spec init(map()) -> state().

init_aux(Name)

-spec init_aux(atom()) -> portunus_machine_aux:aux().

lease_view/1

-spec lease_view(state()) -> portunus_machine_aux:lease_view().

overview(State)

-spec overview(state()) -> map().

query_contenders(LockKey, State)

-spec query_contenders(lock_key(), state()) -> [owner()].

query_owner(LockKey, State)

-spec query_owner(lock_key(), state()) -> {ok, owner_info()} | {error, not_held}.

query_ready_nodes(State)

-spec query_ready_nodes(state()) -> [node()].

query_status(State)

-spec query_status(state()) -> map().

state_enter/2

-spec state_enter(ra_server:ra_state() | eol, state()) -> ra_machine:effects().

token_info(Token)

-spec token_info(token()) -> #{epoch := non_neg_integer(), index := non_neg_integer()}.

Decompose an epoch-packed identifier (a fencing token, an auto-assigned lease id, or a watch reference) for logging and debugging. An epoch of 0 means the identifier was minted before the incarnation had a stamp.

version()

-spec version() -> ra_machine:version().

which_module(Version)

-spec which_module(ra_machine:version()) -> module().