portunus_machine (portunus v0.14.0)
View SourceThe portunus Ra state machine. It manages leases, locks, fencing
tokens, and a score-ordered succession queue (FIFO among equal scores).
Key decisions:
apply/3never reads node-local time, only the leader-stampedsystem_timein the command metadata.- Never uses
make_ref/0orself/0. Instead it derives tokens and IDs from the Raft logindex, packed with a per-incarnation epoch (think restarts; seetoken_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
-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()}.
-type lease_id() :: term().
-type lock_key() :: term().
-type owner() :: term().
-opaque state()
-type token() :: non_neg_integer().
-type watch_ref() :: non_neg_integer().
Functions
-spec apply(ra_machine:command_meta_data(), command(), state()) -> {state(), term(), ra_machine:effects()}.
-spec handle_aux(ra_server:ra_state(), term(), term(), portunus_machine_aux:aux(), ra_aux:internal_state()) -> {no_reply, portunus_machine_aux:aux(), ra_aux:internal_state()} | {no_reply, portunus_machine_aux:aux(), ra_aux:internal_state(), ra_machine:effects()} | {reply, term(), portunus_machine_aux:aux(), ra_aux:internal_state()}.
-spec init_aux(atom()) -> portunus_machine_aux:aux().
-spec lease_view(state()) -> portunus_machine_aux:lease_view().
-spec query_owner(lock_key(), state()) -> {ok, owner_info()} | {error, not_held}.
-spec state_enter(ra_server:ra_state() | eol, state()) -> ra_machine:effects().
-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.
-spec version() -> ra_machine:version().
-spec which_module(ra_machine:version()) -> module().