disk-load retires the readings of a module whose key went while its watch was down (review round 2)
mesh/delivery merged: source.main moved to novox/mesh-catalog@4b16c193f0fb
mesh/delivery-group group feat/data-items merged: every member is merged
mesh/check pass 0c46f47ae57e on 444c2903af7b | unit 4510 run, 4259 kept, 49 skipped | integration 4589 run, 4361 kept, 51 skipped | over bound: unit, …

A key deleted while disk-load was not watching (a restart, a reboot, a machine asleep) never
arrives as a delete. Once the watch has handed over every current key, the readings of modules
with no key are retired once; an empty set is skipped and said, since the engine has not written.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-01 00:30:39 +01:00
parent 6fe0ef321d
commit 0c46f47ae5
3 changed files with 70 additions and 2 deletions
+37 -2
View File
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/health"
"os"
"os/exec"
@@ -286,13 +287,47 @@ func (k *Keeper) MeasureModule(ctx context.Context, module string) {
// 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) {
return k.retire(func(r health.Reading) bool { return r.Module == module }, declared)
}
// RetireUnkeyed retires, once the watch handed over every current key, the readings of this node whose module has no
// key in that set: a key deleted while the watch was not up (disk-load restarted, the machine rebooted or slept) never
// reaches disk-load as a delete. An empty set is skipped and said: the engine has not written its keys yet, and an
// empty set would retire every reading.
func (k *Keeper) RetireUnkeyed(ctx context.Context) {
k.mu.Lock()
defer k.mu.Unlock()
items, err := k.Items(ctx)
if err != nil {
k.Say("the readings of modules with no key are not looked for: %v", err)
return
}
if len(items) == 0 {
k.Say("the node-engine's state %s holds no key of this machine yet: no reading is retired for it", delivery.EngineDataBucket)
return
}
var declared []catalogue.PlacedDataItem
for _, its := range items {
declared = append(declared, its...)
}
n, err := k.retire(func(r health.Reading) bool { _, keyed := items[r.Module]; return !keyed }, declared)
switch {
case err != nil:
k.Say("the readings of modules with no key are not all retired: %v", err)
case n > 0:
k.Say("%d reading(s) of modules with no key of this machine retired, measured no more", n)
}
}
// retire marks retired every reading of this node that of picks and declared does not name.
func (k *Keeper) retire(of func(health.Reading) bool, declared []catalogue.PlacedDataItem) (int, error) {
keys, err := k.Store.Keys()
if err != nil {
return 0, err
}
declaredNow := map[string]bool{}
for _, it := range declared {
declaredNow[health.ReadingKey(k.Node, module, it.ID)] = true
declaredNow[health.ReadingKey(k.Node, it.Module, it.ID)] = true
}
now := k.Measurer.Now().UTC()
n := 0
@@ -305,7 +340,7 @@ func (k *Keeper) retireGone(module string, declared []catalogue.PlacedDataItem)
continue
}
r, err := health.DecodeReading(raw)
if err != nil || r.Node != k.Node || r.Module != module || !r.RetiredAt.IsZero() {
if err != nil || r.Node != k.Node || !of(r) || !r.RetiredAt.IsZero() {
continue
}
r.RetiredAt = now