Currently in testing: the GitHub code will be opened once complete.

Replicated Commands

Every lock-state change is replicated as one of the commands below, carried as a log entry in AppendEntries, and applied by every node in the same order after a majority commit.

Commands (proposed by the leader)

CommandFieldsMeaning
Grantkey, lease_msAcquire a key. The token isn't in the command — each node's state machine assigns it deterministically at apply time, so every node computes the same token
ReleasekeyRelease a key
Expirekey, tokenLeader-driven lease expiry. Removed only if token matches — a late or duplicate expiry can't delete a lock that was freshly granted in the meantime (idempotent)

Applied results (state machine → leader)

ResultMeaning
Granted { token }Acquire succeeded — the issued fencing token
GrantRejectedA Grant arrived for a key already held (defensive — never happens in the normal flow)
ReleasedReleased
NotFoundThe key to release didn't exist
Expired { existed }Expiry processed — existed says whether it was actually removed
NoopNothing happened

Each node tracks a lease's remaining time with its own local clock, but the actual removal always happens only via a committed Expire command — which is why clock drift between nodes can never split the lock state. If a client disconnects before receiving its response, the leader proposes a Release for itself right after granting, to clean up the ghost lock immediately.

Flow — Replicating an Expire

Leader
Follower 1
Follower 2
detects lease expiry via its local clock
AppendEntries · Expire (key, token)
AppendEntries · Expire (key, token)
OK
majority commit → every node applies in the same order
token mismatch → ignored — protects a freshly granted lock
consensus RPC (Raft)

Even expiry is just another command that goes through the leader's proposal → majority commit. Thanks to the token check, even if the same key was re-granted while a commit was delayed, a late-applied Expire can't delete the new lock.

Terminology

  • state machine — the part that applies committed commands in order to produce the current state (here, the lock table). Since every node applies the same commands in the same order, they always converge on the same state.
  • commit — the "final" status a state reaches once a majority of nodes has recorded it. Only committed commands get applied to the state machine.
  • idempotent — a property where applying the same command twice by mistake doesn't change the outcome.