A wait the watchman mirrors keeps its earliest start while the same mark stands, so a key written again with new words does not start the bound again (ADR 0603 §8)

This commit is contained in:
2026-11-03 17:26:45 +01:00
parent 9da14440e8
commit 45bd63427f
4 changed files with 41 additions and 8 deletions
@@ -476,6 +476,14 @@ func TestTheWatchmansFindings(t *testing.T) {
if len(o.pulses) != 3 || len(o.findings) != 0 {
t.Fatalf("pulses %v findings %v", o.pulses, o.findings)
}
// The keys are written again as their parts move on, with new words and a new time: the wait still began at t0.
c.t = c.t.Add(20 * time.Minute)
held2, _ := delivery.EncodeWant(delivery.Want{FP: fpOld, Self: true, Newest: fpNew, Rule: delivery.RuleCanary, Words: "the canary ace runs it, not healthy yet"})
asked2, _ := provisions.Encode(provisions.Provision{Provision: "oidc", Consumer: provisions.End{Module: "photos", Node: "ace"},
Provider: provisions.End{Module: "keycloak", Node: "novox"}, State: provisions.Asked, Why: "the provider is applying", At: c.t})
w.Want("want.ace.web", held2, Put, c.t)
w.Provision("ace.photos.oidc", asked2, Put)
w.Build(bkey, queued, Put, c.t)
for bucket, key := range waits {
expire(w, c, pulseKeyOf(t, bucket, key), 31*time.Minute)
}
@@ -102,13 +102,16 @@ func (w *Watchman) Want(key string, value []byte, op Op, at time.Time) {
at = w.now()
}
w.held[key] = true
w.mirrorWithin(w.stepBound, delivery.AssignerWantsBucket, key,
w.mirrorKept(delivery.AssignerWantsBucket, key,
describe("the hold on %s on %s at %s (rule %d) to end, bound %s: %s", module, node, shortFP(want.FP), want.Rule, w.stepBound, want.Words),
"held|"+want.FP+"|"+want.Newest, at, 0)
"held|"+want.FP+"|"+want.Newest, at)
return
}
if w.held[key] {
delete(w.held, key)
if pk, err := delivery.PulseKey(delivery.AssignerWantsBucket, key); err == nil {
delete(w.starts, pk)
}
w.stuckMoved(delivery.AssignerWantsBucket, key) // the hold ended: its finding is cleared
}
if w.have(node, module) == want.FP {
@@ -156,8 +159,8 @@ func (w *Watchman) Build(key string, value []byte, op Op, at time.Time) {
if at.IsZero() {
at = w.now()
}
w.mirrorWithin(w.stepBound, delivery.BuilderBuildsBucket, key,
describe("the package %s to be published before the build of %s at %s, bound %s", b.Waits, module, shortFP(fp), w.stepBound), b.Waits, at, 0)
w.mirrorKept(delivery.BuilderBuildsBucket, key,
describe("the package %s to be published before the build of %s at %s, bound %s", b.Waits, module, shortFP(fp), w.stepBound), b.Waits, at)
}
// Have is a change of a key of the node-engine's have bucket: the fingerprint a node runs of a module. One that names the
@@ -632,7 +632,7 @@ func (w *Watchman) askedWait(key string, value []byte, op Op) {
if p.Why != "" {
words += ": " + p.Why
}
w.mirrorWithin(w.stepBound, bucket, key, words, "asked", since, 0)
w.mirrorKept(bucket, key, words, "asked", since)
}
func sortedKeys(m map[string]string) []string {
@@ -64,8 +64,10 @@ type Watchman struct {
keptSince map[string]time.Time
wants map[string]string // the fingerprint of each want that stands, by want key
haves map[string]string // the fingerprint each node runs of a module, by have key
held map[string]bool // each want key held by a rule (ADR 0603 §8), mirrored as the hold's wait
bound time.Duration
// starts is, by pulse key, the first start of a wait whose mark still stands (mirrorKept).
starts map[string]startMark
held map[string]bool // each want key held by a rule (ADR 0603 §8), mirrored as the hold's wait
bound time.Duration
// findings is every finding written, by key, as written; seededFindings those read at the start and not yet observed
// again.
findings map[string][]byte
@@ -104,7 +106,7 @@ type Watchman struct {
// New is a watchman writing to out, reading the time from now.
func New(out Out, now func() time.Time) *Watchman {
return &Watchman{out: out, now: now, counts: map[string]int{}, presence: map[string]presence{},
pulses: map[string]*pulse{}, stuck: map[string]delivery.Stuck{}, wants: map[string]string{}, haves: map[string]string{}, held: map[string]bool{}, bound: delivery.PulseMoveBound, stepBound: StepBound,
pulses: map[string]*pulse{}, stuck: map[string]delivery.Stuck{}, wants: map[string]string{}, haves: map[string]string{}, held: map[string]bool{}, starts: map[string]startMark{}, bound: delivery.PulseMoveBound, stepBound: StepBound,
findings: map[string][]byte{}, seededFindings: map[string]bool{}, signals: map[string]signalDue{}, away: map[string]bool{},
haveState: map[string]delivery.Have{}, unhealthy: map[string]int{}, advised: map[string]int{}, advisedSince: map[string]time.Time{},
control: map[string]bool{}, failedSince: map[string]time.Time{}, provFailed: map[string]map[string]ProvReport{}, closures: map[string]map[string]time.Time{}, waiting: map[string]map[string]string{},
@@ -162,6 +164,25 @@ func (w *Watchman) mirrorWithin(bound time.Duration, bucket, key, waited, head s
w.pulses[pk] = &pulse{bucket: bucket, key: key, waited: waited, since: since, head: head, token: token}
}
// startMark is when a wait began, for the mark that names it.
type startMark struct {
mark string
since time.Time
}
// mirrorKept is mirrorWithin for a wait that is the key's value standing as it is (ADR 0603 §8): while the same mark stands
// (a provision asked, a want held at the same fingerprints, a build waiting for the same package), a rewrite of the key (new
// words, a new time) does not start the bound again; the earliest start is kept. A different mark is a new wait.
func (w *Watchman) mirrorKept(bucket, key, waited, mark string, since time.Time) {
if pk, err := delivery.PulseKey(bucket, key); err == nil {
if cur, ok := w.starts[pk]; ok && cur.mark == mark && cur.since.Before(since) {
since = cur.since
}
w.starts[pk] = startMark{mark: mark, since: since}
}
w.mirrorWithin(w.stepBound, bucket, key, waited, mark, since, 0)
}
func (w *Watchman) deletePulse(pk string) {
delete(w.pulses, pk)
if err := w.out.DeletePulse(pk); err != nil {
@@ -176,6 +197,7 @@ func (w *Watchman) moved(bucket, key string) {
if err != nil {
return
}
delete(w.starts, pk)
if _, ok := w.pulses[pk]; ok {
w.deletePulse(pk)
}