Merge main into fix/build-cpu-yields
This commit is contained in:
+78
-1
@@ -51,6 +51,8 @@ type haveState struct {
|
||||
// userList is what the bus node's engine last wrote of the bus's user list, by the module whose have says it (novox/hq
|
||||
// ADR 0547 §8); kept apart from the module's fact, which every apply replaces.
|
||||
userList map[string]delivery.UserListWritten
|
||||
// userListNoKey is each module whose user list was said to have no have key to ride on, so it is said once.
|
||||
userListNoKey map[string]bool
|
||||
// unjudged is each module whose composition moved (an apply began, or the fact names another build) and that no look
|
||||
// has judged since it stopped moving: its health is not yet judged, never the last look's of what ran before.
|
||||
unjudged map[string]bool
|
||||
@@ -283,7 +285,7 @@ func (q *Session) writeHaves(ctx context.Context, bus Bus) {
|
||||
s := &q.haves
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.modules == nil && s.node == nil && len(s.removed) == 0 {
|
||||
if s.modules == nil && s.node == nil && len(s.removed) == 0 && len(s.userList) == 0 {
|
||||
return
|
||||
}
|
||||
node := q.Membership.Node
|
||||
@@ -340,6 +342,9 @@ func (q *Session) writeHaves(ctx context.Context, bus Bus) {
|
||||
if !q.writeRemoved(write, bucket, node) {
|
||||
return
|
||||
}
|
||||
if !q.writeUserListsAlone(write, bucket, node) {
|
||||
return
|
||||
}
|
||||
if s.node != nil {
|
||||
key, err := delivery.HaveNodeKey(node)
|
||||
if err == nil {
|
||||
@@ -455,6 +460,78 @@ func (q *Session) writeRemoved(ctx context.Context, b HaveBucket, node string) b
|
||||
return true
|
||||
}
|
||||
|
||||
// writeUserListsAlone writes the user list of each module this node-engine holds no fact of (no apply here said its have)
|
||||
// into that module's key as the bucket holds it, keeping every other field the key says (novox/hq ADR 0547 §8). The have
|
||||
// of a module is one value with parts from more than one source: the apply's (fingerprint, digest, build, phase, health)
|
||||
// and the user-list follower's. Written only on the pass over the modules this engine applied, the follower's part was
|
||||
// lost whenever the bus module's apply was not this engine's to say (2026-10-26 on the bus node: the bus module had no
|
||||
// want and had left the declaration, so its key kept an older engine's value and the vault read no hashes in it). A key
|
||||
// that does not exist is not created: a have says a phase, and nothing here knows it; that is said once. False when the
|
||||
// pass should stop. The caller holds the lock of the haves.
|
||||
func (q *Session) writeUserListsAlone(ctx context.Context, b HaveBucket, node string) bool {
|
||||
s := &q.haves
|
||||
alone := make([]string, 0, len(s.userList))
|
||||
for module := range s.userList {
|
||||
_, held := s.modules[module]
|
||||
_, gone := s.removed[module]
|
||||
if !held && !gone {
|
||||
alone = append(alone, module)
|
||||
}
|
||||
}
|
||||
sort.Strings(alone)
|
||||
for _, module := range alone {
|
||||
key, err := delivery.HaveKey(node, module)
|
||||
if err != nil {
|
||||
q.sayHave(fmt.Sprintf("module %q has no key in the have bucket: %v", module, err))
|
||||
continue
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
q.sayHave(fmt.Sprintf("the time for writing the have bucket ran out before %s; the next apply or look writes it", key))
|
||||
return false
|
||||
}
|
||||
current, revision, err := b.Get(ctx, key)
|
||||
switch {
|
||||
case errors.Is(err, ErrHaveAbsent):
|
||||
if !s.userListNoKey[module] {
|
||||
if s.userListNoKey == nil {
|
||||
s.userListNoKey = map[string]bool{}
|
||||
}
|
||||
s.userListNoKey[module] = true
|
||||
q.sayHave(fmt.Sprintf("the bus's user list written for %s is in no have: %s does not exist and no apply on "+
|
||||
"this node says %s's phase, so it is written once one does", module, key, module))
|
||||
}
|
||||
continue
|
||||
case err != nil:
|
||||
q.sayHave(fmt.Sprintf("could not read %s in the have bucket, and wrote nothing more there: %v", key, err))
|
||||
q.haveFailed(err)
|
||||
return false
|
||||
}
|
||||
delete(s.userListNoKey, module)
|
||||
h, err := delivery.DecodeHave(current)
|
||||
if err != nil {
|
||||
q.sayHave(fmt.Sprintf("the bus's user list is not written in %s: its value cannot be read: %v", key, err))
|
||||
continue
|
||||
}
|
||||
w := s.userList[module]
|
||||
next := h
|
||||
next.UserList = &w
|
||||
if delivery.SameHave(h, next) {
|
||||
continue
|
||||
}
|
||||
raw, err := delivery.EncodeHave(next)
|
||||
if err != nil {
|
||||
q.sayHave(fmt.Sprintf("cannot encode %s: %v", key, err))
|
||||
continue
|
||||
}
|
||||
if err := b.Set(ctx, key, raw, revision); err != nil {
|
||||
q.sayHave(fmt.Sprintf("could not write %s in the have bucket; the next apply or look writes it again: %v", key, err))
|
||||
q.haveFailed(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// putHave reads a key and writes it unless same says it already holds the value. False when the pass should stop: the
|
||||
// bucket failed, the error is said once, and the rest wait for the next pass.
|
||||
func (q *Session) putHave(ctx context.Context, b HaveBucket, key string, value []byte, same func([]byte) bool) bool {
|
||||
|
||||
@@ -660,3 +660,57 @@ func TestADeclarationReportMovesAHealthAndARemovalForgetsIt(t *testing.T) {
|
||||
t.Fatal("a removed module is still marked unjudged")
|
||||
}
|
||||
}
|
||||
|
||||
// ADR 0547 §8, 2026-10-26 on the bus node: the bus's user list of a module this engine says no have of (no want, and no
|
||||
// longer in the declaration) still reaches the module's key, merged into what the key says, every other field kept; a
|
||||
// key that does not exist is not made up and is said once; a writer that drops the list again is written over.
|
||||
func TestAUserListReachesTheHaveOfAModuleNoApplyHereSays(t *testing.T) {
|
||||
l := newHaveLink()
|
||||
older := delivery.Have{V: delivery.Version, Build: "build-1", Phase: delivery.PhaseRunning, Health: delivery.HealthOK}
|
||||
l.put(t, "n1.nats", older)
|
||||
q, said := aHaveQueue(l)
|
||||
dig := "sha256:" + strings.Repeat("c", 64)
|
||||
w := delivery.UserListWritten{Source: "n1.nats", Revision: 7, Wanted: dig, Digest: dig, Users: 58,
|
||||
At: time.Date(2026, 10, 26, 12, 26, 51, 0, time.UTC)}
|
||||
q.NoteUserList(context.Background(), "nats", w)
|
||||
got := l.have(t, "n1.nats")
|
||||
if got.UserList == nil || got.UserList.Users != 58 || got.UserList.Digest != dig {
|
||||
t.Fatalf("the user list is not in the have: %+v", got)
|
||||
}
|
||||
if got.Build != "build-1" || got.Phase != delivery.PhaseRunning || got.Health != delivery.HealthOK {
|
||||
t.Fatalf("the merge lost the key's own fields: %+v", got)
|
||||
}
|
||||
rev := l.kv["n1.nats"].rev
|
||||
q.NoteUserList(context.Background(), "nats", w)
|
||||
if l.kv["n1.nats"].rev != rev {
|
||||
t.Errorf("the same list was written again, to revision %d", l.kv["n1.nats"].rev)
|
||||
}
|
||||
|
||||
// Another writer replaces the value without the list: the next pass merges it back.
|
||||
l.put(t, "n1.nats", older)
|
||||
q.SayFacts(context.Background(), Report{})
|
||||
if got := l.have(t, "n1.nats"); got.UserList == nil || got.Build != "build-1" {
|
||||
t.Fatalf("a value written without the list kept it out: %+v", got)
|
||||
}
|
||||
|
||||
// No key at all: nothing is created, said once; the module's first have carries the list.
|
||||
q.NoteUserList(context.Background(), "other", w)
|
||||
q.NoteUserList(context.Background(), "other", w)
|
||||
if _, ok := l.kv["n1.other"]; ok {
|
||||
t.Fatalf("a have was made up for a module no apply says")
|
||||
}
|
||||
n := 0
|
||||
for _, s := range *said {
|
||||
if strings.Contains(s, "is in no have") {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n != 1 {
|
||||
t.Errorf("the missing key was said %d times: %q", n, *said)
|
||||
}
|
||||
fp := "src2:" + strings.Repeat("a", 64)
|
||||
q.NoteModule(context.Background(), ModuleFact{Module: "other", FP: fp, Composed: dig})
|
||||
if got := l.have(t, "n1.other"); got.UserList == nil || got.FP != fp {
|
||||
t.Fatalf("the first have of the module lost the list: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user