disk-load retires a reading only on its module's key, and measures as soon as its watch catches up (review round 1)

A round no longer retires readings missing from the watched set, which may be empty or partial;
a module's readings are retired when its key of mesh-host_data is deleted or names fewer items.
A round refused before the watch caught up runs once it has, not an hour later. disk_round reads
this machine's key alone, as its grant allows.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-01 00:28:29 +01:00
parent 357a53d9ac
commit 6fe0ef321d
5 changed files with 56 additions and 36 deletions
+34 -22
View File
@@ -13,6 +13,7 @@ import (
"sort"
"strings"
"sync"
"sync/atomic"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
@@ -59,6 +60,19 @@ type Keeper struct {
pmu sync.Mutex
pending map[string]bool
wake chan struct{}
// unwatched says a round was refused because the watch had not caught up (ErrNotWatchedYet): one runs as soon as
// it has (Watched.Follow), not an hour later.
unwatched atomic.Bool
}
// RoundIfRefused runs one round now when the last was refused for a watch not caught up, and says whether it did.
func (k *Keeper) RoundIfRefused(ctx context.Context) bool {
if !k.unwatched.CompareAndSwap(true, false) {
return false
}
_, _, _ = k.Round(ctx, false)
return true
}
// SayEvery is how many items a round measures between two lines saying how far it got.
@@ -120,6 +134,9 @@ func (k *Keeper) Round(ctx context.Context, walks bool) (written, kept int, err
k.Say("a round of measuring the data declared here starts (walks: %v)", walks)
items, err := k.Items(ctx)
if err != nil {
if errors.Is(err, ErrNotWatchedYet) {
k.unwatched.Store(true)
}
r.Error = err.Error()
end()
k.Say("the round measured nothing: %v", err)
@@ -164,13 +181,8 @@ func (k *Keeper) Round(ctx context.Context, walks bool) (written, kept int, err
}
}
}
retired, err := k.retireGone(items)
written += retired
r.Written = written
if err != nil {
failed = append(failed, err.Error())
r.Failed = append(r.Failed, FailedItem{Item: "the readings no longer declared", Why: err.Error()})
}
// A round retires nothing: a reading is retired only when its module's key says so (MeasureModule), never because
// an item is missing from what the watch holds now, which may be empty or partial.
end()
k.Say("the round ended: %d of %d items measured, %d readings written, %d unchanged, %d failed", r.Measured, r.Items,
written, kept, len(r.Failed))
@@ -237,13 +249,13 @@ func (k *Keeper) MeasureModule(ctx context.Context, module string) {
k.Say("%s's data items changed and are not measured: %v", module, err)
return
}
n, err := k.retireGone(module, items[module])
if err != nil {
k.Say("%s's readings of items it no longer declares here are not retired: %v", module, err)
} else if n > 0 {
k.Say("%s no longer declares %d item(s) here: their readings are retired, measured no more", module, n)
}
if len(items[module]) == 0 {
n, err := k.retireGone(items)
if err != nil {
k.Say("%s declares no data item here any more, and its readings are not retired: %v", module, err)
return
}
k.Say("%s declares no data item here any more: %d reading(s) retired, measured no more", module, n)
return
}
written, kept := 0, 0
@@ -268,19 +280,19 @@ func (k *Keeper) MeasureModule(ctx context.Context, module string) {
kept, len(failed), map[bool]string{true: " (" + strings.Join(failed, "; ") + ")", false: ""}[len(failed) > 0])
}
// retireGone marks retired every reading of this node whose item the node no longer declares — its module was unassigned
// there, or is no longer pulled in — with when and why, and keeps it: the record of what the module left behind, until a
// person deletes it (ADR 0233). It is cleared when the item is declared again (measureOne).
func (k *Keeper) retireGone(declared map[string][]catalogue.PlacedDataItem) (int, error) {
// retireGone marks retired every reading of this node of module whose item its key of the engine's state data no longer
// names — the key was deleted (the module was unassigned there, or is no longer pulled in) or names fewer items — with
// when and why, and keeps it: the record of what the module left behind, until a person deletes it (ADR 0233). It is
// asked only on a change of that module's key (novox/hq ADR 0577 clause 2), never from a round. It is cleared when the
// item is declared again (measureOne).
func (k *Keeper) retireGone(module string, declared []catalogue.PlacedDataItem) (int, error) {
keys, err := k.Store.Keys()
if err != nil {
return 0, err
}
declaredNow := map[string]bool{}
for module, its := range declared {
for _, it := range its {
declaredNow[health.ReadingKey(k.Node, module, it.ID)] = true
}
for _, it := range declared {
declaredNow[health.ReadingKey(k.Node, module, it.ID)] = true
}
now := k.Measurer.Now().UTC()
n := 0
@@ -293,7 +305,7 @@ func (k *Keeper) retireGone(declared map[string][]catalogue.PlacedDataItem) (int
continue
}
r, err := health.DecodeReading(raw)
if err != nil || r.Node != k.Node || !r.RetiredAt.IsZero() {
if err != nil || r.Node != k.Node || r.Module != module || !r.RetiredAt.IsZero() {
continue
}
r.RetiredAt = now