Hold back the node's own apply while a module's composition cannot be read or is refused, naming it (issue 1944, issue 1945)
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
@@ -189,6 +190,8 @@ func (a *Agent) Run(ctx context.Context) error {
|
|||||||
compsCaught = true
|
compsCaught = true
|
||||||
} else if m := a.takeComposition(u); m != "" {
|
} else if m := a.takeComposition(u); m != "" {
|
||||||
dirty[m] = true
|
dirty[m] = true
|
||||||
|
// The node's own may wait for this one to read (unreadableModules): looked at again with it.
|
||||||
|
dirty[NodeModule] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Nothing is applied until what was there at the start is all in: a module's composition may arrive after its
|
// Nothing is applied until what was there at the start is all in: a module's composition may arrive after its
|
||||||
@@ -389,6 +392,24 @@ func (a *Agent) others(except string) []delivery.Composed {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unreadableModules are the keys of the module compositions in the node's bucket that cannot be read or are refused, of a
|
||||||
|
// module the assigner has not removed. Called holding a.mu.
|
||||||
|
func (a *Agent) unreadableModules() []string {
|
||||||
|
var out []string
|
||||||
|
for key, c := range a.comps {
|
||||||
|
_, m, _, isNode, err := delivery.ParseComposedKey(key)
|
||||||
|
if err != nil || isNode || c.Err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if w := a.wants[m]; w.Present && w.Want.Removed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, key)
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// heldOut is what an apply or a removal holds out: others, and every module whose own composition the node's bucket holds
|
// heldOut is what an apply or a removal holds out: others, and every module whose own composition the node's bucket holds
|
||||||
// and the agent does not run yet. A module that left the node's own composition for one of its own keeps its resources
|
// and the agent does not run yet. A module that left the node's own composition for one of its own keeps its resources
|
||||||
// until that one is applied, so the node's own apply never removes them in between (issue 1945, live on 2026-10-25:
|
// until that one is applied, so the node's own apply never removes them in between (issue 1945, live on 2026-10-25:
|
||||||
@@ -502,6 +523,16 @@ func (a *Agent) settleOne(ctx context.Context, module string) {
|
|||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
return // held for a failed backup, and no answer since
|
return // held for a failed backup, and no answer since
|
||||||
}
|
}
|
||||||
|
if module == NodeModule {
|
||||||
|
// Not while a module's composition cannot be read or is refused: what it carries may be what left the node's own,
|
||||||
|
// and the node's own apply would remove it, held out by nothing (issue 1945, the review's gap 2).
|
||||||
|
if bad := a.unreadableModules(); len(bad) > 0 {
|
||||||
|
a.sayOnce("node-waits:"+strings.Join(bad, ","), fmt.Sprintf("the node's own composition is not applied while "+
|
||||||
|
"%s cannot be read or is refused: what it carries would be removed", strings.Join(bad, ", ")))
|
||||||
|
a.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
others := a.heldOut(module)
|
others := a.heldOut(module)
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
a.apply(ctx, module, w, comp, others)
|
a.apply(ctx, module, w, comp, others)
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ type bed struct {
|
|||||||
backup func(module string) Backup
|
backup func(module string) Backup
|
||||||
kept map[string][]json.RawMessage
|
kept map[string][]json.RawMessage
|
||||||
others [][]delivery.Composed
|
others [][]delivery.Composed
|
||||||
|
said []string
|
||||||
env Env
|
env Env
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +149,12 @@ func newBed(t *testing.T) *bed {
|
|||||||
},
|
},
|
||||||
Kept: func() map[string][]json.RawMessage { return b.kept },
|
Kept: func() map[string][]json.RawMessage { return b.kept },
|
||||||
Trusted: func() []ed25519.PublicKey { return []ed25519.PublicKey{controllerPub, operatorPub} },
|
Trusted: func() []ed25519.PublicKey { return []ed25519.PublicKey{controllerPub, operatorPub} },
|
||||||
Say: func(s string) { t.Log(s) },
|
Say: func(s string) {
|
||||||
|
t.Log(s)
|
||||||
|
b.mu.Lock()
|
||||||
|
b.said = append(b.said, s)
|
||||||
|
b.mu.Unlock()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
@@ -718,3 +724,39 @@ func TestARemovedModulesCompositionHoldsNothingBack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The node's own composition is not applied while a module's composition in the node's bucket cannot be read or is not
|
||||||
|
// signed by a trusted key: what that one carries may be what left the node's own, and the node's own apply would remove
|
||||||
|
// it (issue 1945, the review's gap 2). It is said, naming the composition, and applied once that one reads.
|
||||||
|
func TestTheNodesOwnWaitsWhileAModulesCompositionCannotBeRead(t *testing.T) {
|
||||||
|
b := newBed(t)
|
||||||
|
bad, err := delivery.ComposedKey(node, "broken", fpOf("c"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b.comps.before(bad, []byte(`{"v":1,"not":"a composition"}`))
|
||||||
|
nk, nraw, ndig := composition(t, NodeModule, "", false)
|
||||||
|
nwk, _ := delivery.WantNodeKey(node)
|
||||||
|
nwraw, _ := delivery.EncodeNodeWant(delivery.NodeWant{Composed: ndig})
|
||||||
|
b.comps.before(nk, nraw)
|
||||||
|
b.wants.before(nwk, nwraw)
|
||||||
|
stop := b.run()
|
||||||
|
defer stop()
|
||||||
|
eventually(t, "the wait not said", func() bool {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
for _, s := range b.said {
|
||||||
|
if strings.Contains(s, bad) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if got := b.appliedList(); len(got) != 0 {
|
||||||
|
t.Fatalf("applied %v while a composition could not be read", got)
|
||||||
|
}
|
||||||
|
// Read again, the node's own is applied.
|
||||||
|
gk, graw, _ := composition(t, "broken", fpOf("c"), false, file("broken", "x", "1"))
|
||||||
|
b.comps.put(gk, graw)
|
||||||
|
eventually(t, "the node's own not applied", func() bool { return len(b.appliedList()) == 1 })
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user