mule/runtime/peer_database
The Postgres lease-election peer — a faithful port of
Oban.Peers.Database. A self-rescheduling actor that competes for the
single mule_peers row for its instance name: it acquires the lease when
free, renews it while held, and lets it expire to hand leadership off. On
every tick it writes its current leadership into the cell (peer), which
the cron/pruner/stager gates read without ever calling this process.
Key correctness properties (see the C-P spec):
- The first election runs synchronously in the initialiser, so the cell
is correct before any gate can read it and a restarted leader that still
owns its row re-caches
Trueimmediately rather than stalling a full interval. - Leadership is decided by the database (
elect_leadercompares the storednodeto ours), never by a row count, so a renew never reports leader for a row another node owns. - A clean
Ok(False)flips leadership off at once (the lease is the source of truth). “Keep previous” applies only to transaction errors, and only untilmax_consecutive_errorsticks have failed — past that we demote, because the lease has surely lapsed in the database and another node may have legitimately taken over. - The leader renews at
interval / leader_boost, comfortably inside the lease TTL so a slow tick (theelect_leadercall can take up to its pool timeout) does not let the lease lapse under load.
Types
pub type Message {
Elect
LeaderHeard(notifier.Notification)
Shutdown
}
Constructors
-
Elect -
LeaderHeard(notifier.Notification) -
Shutdown
pub type State {
State(
name: String,
node: String,
engine: engine.Engine,
notifier: notifier.Notifier,
interval: duration.Duration,
is_leader: Bool,
consecutive_errors: Int,
self_subject: process.Subject(Message),
)
}
Constructors
-
State( name: String, node: String, engine: engine.Engine, notifier: notifier.Notifier, interval: duration.Duration, is_leader: Bool, consecutive_errors: Int, self_subject: process.Subject(Message), )
Values
pub fn child(
name name: String,
engine engine: engine.Engine,
notifier notifier: notifier.Notifier,
node node: String,
interval interval: duration.Duration,
) -> supervision.ChildSpecification(Nil)
Supervise the peer actor as a single worker. Takes only what the actor needs
(name, engine, notifier, node, interval) rather than the whole Config, so
it does not depend on the peer.Peer that Config carries — breaking the
startup cycle.
pub fn reader(
name name: String,
engine engine: engine.Engine,
) -> peer.Peer
Build the Peer read surface for the Postgres backend: leadership is the
pure cell read (the gate hot path), and get_leader queries the database
(engine.read_leader), exactly as Oban.Peer.get_leader does.
pub fn start(
name: String,
engine: engine.Engine,
notifier: notifier.Notifier,
node: String,
interval: duration.Duration,
) -> Result(
actor.Started(process.Subject(Message)),
actor.StartError,
)
Start the peer actor directly (the child spec wraps this). Public so a
test can hold the actor’s Subject and address its process.