mirror of
https://github.com/hanzoai/ha.git
synced 2026-09-30 22:38:49 +00:00
ha: the seam is Leases, named for what it yields
It was Fencer, which named the wrong actor. Fencing is what the STORE does when it refuses a stale round; an interface that hands back a lease fences nothing. The value it yields is a Lease, so it is Leases -- the same shape as db.Namespaces resolving a namespace to a handle. Recorded in the doc why it is not the other candidates, because each is tempting and each is wrong in an instructive way: Writer has no write method and already means something you write TO; Consensus names one possible source when StaticLeases returns Round 1 from a single process and CAS consults a single register, neither reaching agreement; Leader implies one leader when Owner is per-KEY, so a thousand keys have a thousand owners. Prose still says fencing where it describes the store refusing a stale round. That is the accurate use, and keeping it is what makes the rename legible. Identifiers only -- no behaviour change. Breaking for importers, so this is a minor bump and cloud moves on its own commit. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -14,23 +14,31 @@ package ha
|
||||
// advanced it (see github.com/hanzoai/vfs/replica.FencedStore).
|
||||
//
|
||||
// This package contributes only the composable VALUE (Lease) and the SEAM
|
||||
// (Fencer). It deliberately does NOT produce the round: a round is the output of
|
||||
// (Leases) — named for what it yields, not for what consumes it. It is NOT
|
||||
// "Fencer": fencing is what the STORE does when it refuses a stale round, and an
|
||||
// interface that issues a lease does not fence anything. It is NOT "Writer": it
|
||||
// has no write method, and Writer already means something you write TO. It is NOT
|
||||
// "Consensus": StaticLeases returns Round 1 from a single process and a CAS
|
||||
// implementation consults one register — neither reaches agreement, and naming the
|
||||
// seam after one possible source is the mistake this separation exists to avoid.
|
||||
// It is NOT "Leader": Owner is per-KEY, so a thousand keys have a thousand owners
|
||||
// and there is no single leader to name. It deliberately does NOT produce the round: a round is the output of
|
||||
// a LINEARIZABLE source, and a linearizable source is coordination — folding it
|
||||
// in would re-complect election with consensus, the precise separation this
|
||||
// package exists to keep. Today that source is a single linearizable register
|
||||
// (an object-store compare-and-set, or a coordination DB); tomorrow it is a
|
||||
// BFT-agreed round (the Lux quasar RSM). Both plug in behind Fencer with no
|
||||
// BFT-agreed round (the Lux quasar RSM). Both plug in behind Leases with no
|
||||
// change at any call site.
|
||||
//
|
||||
// The one invariant every Fencer MUST uphold, on which all downstream safety
|
||||
// rests:
|
||||
// The one invariant every implementation MUST uphold, on which all downstream
|
||||
// safety rests:
|
||||
//
|
||||
// The round is monotone non-decreasing per key across ALL callers, and STRICTLY
|
||||
// increases whenever the writer role changes hands. A newly-acquired lease
|
||||
// therefore always outranks every prior holder's, so the store can fence a
|
||||
// deposed writer simply by refusing any round below the highest it has admitted.
|
||||
//
|
||||
// Composition: Owner() picks who; Fencer binds that who to a monotone round;
|
||||
// Composition: Owner() picks who; Leases binds that who to a monotone round;
|
||||
// the store admits a write iff its round is current. Each concern stays in its
|
||||
// lane — this file adds a value and an interface, nothing that does I/O.
|
||||
|
||||
@@ -53,7 +61,7 @@ type Lease struct {
|
||||
Round Round // the monotone round the owner stamps onto its writes.
|
||||
}
|
||||
|
||||
// Fencer issues the caller's current Lease for a key from a linearizable,
|
||||
// Leases issues the caller's current Lease for a key from a linearizable,
|
||||
// monotone round source. It is the seam a consensus-backed round drops into with
|
||||
// no change at call sites; the interim implementation reads and advances a round
|
||||
// in a single linearizable store. This package defines the seam and the value
|
||||
@@ -62,7 +70,7 @@ type Lease struct {
|
||||
// A caller MUST fail CLOSED on an error: no Lease means no established round, and
|
||||
// writing without a fresh round risks two writers at one epoch. A nil error is
|
||||
// the only signal it is safe to write under.
|
||||
type Fencer interface {
|
||||
type Leases interface {
|
||||
// Acquire returns the caller's Lease for key: itself as Owner, bound to the
|
||||
// round at which it currently holds the writer role. Implementations advance
|
||||
// the round when the role changes hands TO the caller and keep it on renewal,
|
||||
@@ -73,17 +81,17 @@ type Fencer interface {
|
||||
Acquire(ctx context.Context, key string) (Lease, error)
|
||||
}
|
||||
|
||||
// StaticFencer is the single-process Fencer: the sole process is the sole
|
||||
// StaticLeases is the single-process Leases: the sole process is the sole
|
||||
// writer, so every key is held by self at a fixed Round of 1 — exactly-once by
|
||||
// construction, with no round source to consult. It is the correct Fencer for
|
||||
// construction, with no round source to consult. It is the correct Leases for
|
||||
// local dev, a standalone binary, and tests, and the safe default when no
|
||||
// linearizable source is wired — mirroring Static for Membership. A single
|
||||
// process cannot have a deposed second writer, so a constant round is sound;
|
||||
// the moment there are two writers, a real (linearizable) Fencer is required.
|
||||
func StaticFencer(self string) Fencer { return staticFencer(self) }
|
||||
// the moment there are two writers, a real (linearizable) Leases is required.
|
||||
func StaticLeases(self string) Leases { return staticLeases(self) }
|
||||
|
||||
type staticFencer string
|
||||
type staticLeases string
|
||||
|
||||
func (s staticFencer) Acquire(_ context.Context, key string) (Lease, error) {
|
||||
func (s staticLeases) Acquire(_ context.Context, key string) (Lease, error) {
|
||||
return Lease{Key: key, Owner: Member{ID: string(s)}, Round: 1}, nil
|
||||
}
|
||||
|
||||
+7
-7
@@ -10,11 +10,11 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestStaticFencerHoldsEveryKeyAtRoundOne proves the single-process Fencer is
|
||||
// TestStaticFencerHoldsEveryKeyAtRoundOne proves the single-process Leases is
|
||||
// exactly-once by construction: self holds every key at the fixed round 1, never
|
||||
// an error — the sole process is the sole writer.
|
||||
func TestStaticFencerHoldsEveryKeyAtRoundOne(t *testing.T) {
|
||||
f := StaticFencer("solo")
|
||||
f := StaticLeases("solo")
|
||||
for _, key := range []string{"acme", "globex", "orgs/acme/iam", ""} {
|
||||
l, err := f.Acquire(context.Background(), key)
|
||||
if err != nil {
|
||||
@@ -28,9 +28,9 @@ func TestStaticFencerHoldsEveryKeyAtRoundOne(t *testing.T) {
|
||||
|
||||
// TestStaticFencerIsDeterministic proves repeated Acquire of a key returns a
|
||||
// STABLE lease (round does not drift for a stable owner) — the renewal contract
|
||||
// every Fencer must honor.
|
||||
// every Leases must honor.
|
||||
func TestStaticFencerIsDeterministic(t *testing.T) {
|
||||
f := StaticFencer("solo")
|
||||
f := StaticLeases("solo")
|
||||
a, _ := f.Acquire(context.Background(), "acme")
|
||||
b, _ := f.Acquire(context.Background(), "acme")
|
||||
if a != b {
|
||||
@@ -55,6 +55,6 @@ func TestLeaseRoundIsTheFencingBoundary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// staticFencer must satisfy the Fencer seam — the compile-time proof the value
|
||||
// this package ships is usable wherever a Fencer is required.
|
||||
var _ Fencer = StaticFencer("self")
|
||||
// staticLeases must satisfy the Leases seam — the compile-time proof the value
|
||||
// this package ships is usable wherever a Leases is required.
|
||||
var _ Leases = StaticLeases("self")
|
||||
|
||||
Reference in New Issue
Block a user