Malachi. Cluster. LeaseHolder
(malachi v0.8.13)
View Source
The lease client (R0-b): a GenServer that keeps trying to hold a Malachi.Cluster.Lease and runs
the leader-election timer triangle (Kubernetes-style), so exactly one node runs the fenced work.
On each :retry_period_ms tick it calls the injected renew seam (acquire-or-renew):
- a follower that acquires becomes leader and calls
on_acquiredwith the fencing token; - a leader that renews stays leader (recording the local time of the successful renewal);
- a leader told the lease is held by another drops leadership immediately (
on_lost); - a leader that cannot reach the lease keeps trying until
:renew_deadline_mshas passed since its last successful renewal, then proactively drops leadership (on_lost), the k8s OnStoppedLeading: give up before the lease could expire and be stolen, so two nodes never both believe they lead.
The triangle must satisfy lease duration > renew_deadline_ms > retry_period_ms: the holder gives up
(deadline) before the lease expires (duration), leaving a safety margin for another node to take over;
it retries several times (retry period) within the deadline. Elapsed time is measured on the local
clock (:clock), tolerating clock skew as k8s does (assumes NTP). On normal shutdown a leader releases
the lease, so the next holder can take over without waiting for expiry.
Seams (all injected, so the timing logic is testable without ra):
:renew-(-> {:ok, fence} | {:error, :held} | {:error, reason})(:heldmeans another node holds the lease; any other error means it could not be reached/renewed this tick);:release-(fence -> any), best-effort release on shutdown;:on_acquired-(fence -> any), leadership gained;:on_lost-(-> any), leadership lost/dropped;:retry_period_ms(default 2_000),:renew_deadline_ms(default 10_000);:clock-(-> integer())monotonic ms (defaultSystem.monotonic_time/1);:name- optional registered name.
Summary
Functions
Returns a specification to start this module under a supervisor.
Whether this node currently holds the lease. A plain read, does not force a tick.
Starts the holder. See the module doc for required seams.
Runs one election tick synchronously; returns {role, fence} (a manual trigger, e.g. for tests).
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec leader?(GenServer.server()) :: boolean()
Whether this node currently holds the lease. A plain read, does not force a tick.
@spec start_link(keyword()) :: GenServer.on_start()
Starts the holder. See the module doc for required seams.
@spec tick_now(GenServer.server()) :: {:follower | :leader, non_neg_integer() | nil}
Runs one election tick synchronously; returns {role, fence} (a manual trigger, e.g. for tests).