Archived
Act under a lease, keep accounts by order, one writer at composition (hq to-be 45 Phase 2)
Two controllers could both act (issue 204), a reconcile's report could overtake the apply after it and the digest decided (issue 267), and a grant could make a second writer of a machine's report. - The lease (internal/lease, ADR 0229): mesh-controller_lease key `holder`, 15 s age, renewed every 5 s by compare-and-set; the epoch is the revision it was taken at. The gate is the clock (stops 3 s before expiry); a refused renewal is a loss and the process exits; a holder that stops gives it back. serve takes it before asserting the bus. Epochs kept in the store (migration 0068 controller_epoch) as a floor: a bucket raised from nothing is compacted past it. Unleased (no epoch, S12 urgent) only when nobody holds it and the bus will not let it be written. A shell command acts under the holder's epoch, or its own lease when none. - Declarations carry `epoch` inside the signed envelope, only to a machine whose latest account carried a report_sequence (mesh-host #35); would-send is composed with the epoch last sent. Allot and the send both pass the gate. - Reports: contract in internal/link/order.go (epoch, sequence, report_sequence, older_than, refused_older). Accounts kept by epoch, then sequence, then report sequence; older refused, counted; unordered reports keep the digest rule. Plans by compare-and-set on a revision, with epoch. Conditions and calls carry the epoch and are not written off the lease. - S12 and S13 (naming the writer by epoch) watched, D5 run; reset of the bucket said. Writers table compiled in and enforced in PermissionsFor; the controller no longer publishes mesh.control.>. A contract per consumed kind, and the empty-on-error lint over the repository. - mesh-host pinned to its main with the epoch in the validator (D1 validates the envelope as sent). Needs mesh-host's genesis lock with the lease grant (mesh-host PR) for TestTheInstallersFirstUserListIsWhatTheControllerWouldCompose.
This commit is contained in:
@@ -129,9 +129,8 @@ type Condition struct {
|
||||
Resolver string `json:"resolver"`
|
||||
// Silenced is null when no silence is in force: said, not left out, so a reader need not guess.
|
||||
Silenced *Silence `json:"silenced"`
|
||||
// Epoch is the controller lease epoch that last wrote it. Zero until the lease exists (to-be 45
|
||||
// Phase 2): no controller holds an epoch yet, and a number invented here would be one nobody
|
||||
// could compare.
|
||||
// Epoch is the controller lease epoch that last wrote it (to-be 45 §6). Zero where it was written
|
||||
// by a controller serving without the lease, or before the lease existed.
|
||||
Epoch uint64 `json:"epoch"`
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ type Keeper struct {
|
||||
now func() time.Time
|
||||
say func(format string, args ...any)
|
||||
changed func()
|
||||
epoch func() (uint64, error)
|
||||
|
||||
mu sync.Mutex
|
||||
// cleared is when each recently cleared condition cleared and how often it had been raised, so
|
||||
@@ -88,6 +89,9 @@ type Options struct {
|
||||
Say func(format string, args ...any)
|
||||
// Changed is told of every transition, at once — for `status`, which leads with what is open.
|
||||
Changed func()
|
||||
// Epoch is the controller lease this keeper writes under (to-be 45 §6): every write carries its
|
||||
// epoch, and none is made while it is not held. Nil writes with no epoch (a test, the memory store).
|
||||
Epoch func() (uint64, error)
|
||||
}
|
||||
|
||||
// TellFor is how long one transition is offered to the bus before it is said lost.
|
||||
@@ -97,6 +101,7 @@ var TellFor = 10 * time.Minute
|
||||
// that cleared just before this controller started and is raised again now is a reopening.
|
||||
func NewKeeper(ctx context.Context, o Options) *Keeper {
|
||||
k := &Keeper{store: o.Store, history: o.History, teller: o.Teller, now: o.Now, say: o.Say, changed: o.Changed,
|
||||
epoch: o.Epoch,
|
||||
cleared: map[string]clearing{}, out: make(chan Event, 1024), drained: make(chan struct{})}
|
||||
if k.now == nil {
|
||||
k.now = time.Now
|
||||
@@ -137,6 +142,20 @@ func (k *Keeper) Unsaid() int {
|
||||
return k.unsaid
|
||||
}
|
||||
|
||||
// stamp is the gate every write passes: the epoch it carries, set on the condition, or why this
|
||||
// keeper may not write — a controller that lost the lease raises, observes and clears nothing.
|
||||
func (k *Keeper) stamp(c *Condition) error {
|
||||
if k.epoch == nil {
|
||||
return nil
|
||||
}
|
||||
epoch, err := k.epoch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("the condition %s is not written: %w", c.Key, err)
|
||||
}
|
||||
c.Epoch = epoch
|
||||
return nil
|
||||
}
|
||||
|
||||
// tries bounds one compare-and-set: two writers rarely race more than once.
|
||||
const tries = 8
|
||||
|
||||
@@ -174,6 +193,9 @@ func (k *Keeper) Observe(ctx context.Context, o Observation) (Condition, error)
|
||||
}
|
||||
}
|
||||
k.mu.Unlock()
|
||||
if err := k.stamp(&c); err != nil {
|
||||
return Condition{}, err
|
||||
}
|
||||
body, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return Condition{}, err
|
||||
@@ -214,6 +236,9 @@ func (k *Keeper) Observe(ctx context.Context, o Observation) (Condition, error)
|
||||
if len(c.Evidence) > KeptEvidence {
|
||||
c.Evidence = c.Evidence[:KeptEvidence]
|
||||
}
|
||||
if err := k.stamp(&c); err != nil {
|
||||
return Condition{}, err
|
||||
}
|
||||
body, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return Condition{}, err
|
||||
@@ -247,6 +272,9 @@ func (k *Keeper) Clear(ctx context.Context, key, why string) (bool, error) {
|
||||
// Unreadable is not resolved: kept, and said, rather than removed unread.
|
||||
return false, fmt.Errorf("the condition %s on the bus cannot be read, so it is not cleared: %w", key, err)
|
||||
}
|
||||
if err := k.stamp(&c); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := k.store.Delete(ctx, key, entry.Revision); errors.Is(err, ErrMoved) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
@@ -318,6 +346,9 @@ func (k *Keeper) Silence(ctx context.Context, key string, d time.Duration, by, w
|
||||
}
|
||||
now := k.now().UTC()
|
||||
c.Silenced = &Silence{Until: now.Add(d), By: by, Why: strings.TrimSpace(why), Since: now}
|
||||
if err := k.stamp(&c); err != nil {
|
||||
return Condition{}, err
|
||||
}
|
||||
body, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return Condition{}, err
|
||||
@@ -362,6 +393,9 @@ func (k *Keeper) EndSilences(ctx context.Context) error {
|
||||
}
|
||||
was := held.Silenced.Why
|
||||
held.Silenced = nil
|
||||
if err := k.stamp(&held); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := json.Marshal(held)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user