diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 2716419..40f6fe4 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -393,18 +393,39 @@ func (a *Agent) others(except string) []delivery.Composed { } // 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. +// module the assigner has not removed: for each module only the composition its want names, else its newest, since a +// stale one nobody applies (written before a rule the SDK now holds) must not hold the node's own back. Called holding +// a.mu. func (a *Agent) unreadableModules() []string { - var out []string + type pick struct { + key string + c compNow + } + current := map[string]pick{} for key, c := range a.comps { - _, m, _, isNode, err := delivery.ParseComposedKey(key) - if err != nil || isNode || c.Err == nil { + _, m, fp, isNode, err := delivery.ParseComposedKey(key) + if err != nil || isNode { continue } - if w := a.wants[m]; w.Present && w.Want.Removed { + w := a.wants[m] + if w.Present && w.Want.Removed { continue } - out = append(out, key) + if w.Present && w.Want.FP != "" { + if w.Want.FP == fp { + current[m] = pick{key, c} + } + continue + } + if prev, ok := current[m]; !ok || c.Rev > prev.c.Rev { + current[m] = pick{key, c} + } + } + var out []string + for _, p := range current { + if p.c.Err != nil { + out = append(out, p.key) + } } sort.Strings(out) return out diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 31b6b0a..793d9a3 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -760,3 +760,25 @@ func TestTheNodesOwnWaitsWhileAModulesCompositionCannotBeRead(t *testing.T) { b.comps.put(gk, graw) eventually(t, "the node's own not applied", func() bool { return len(b.appliedList()) == 1 }) } + +// A stale refused composition beside a newer readable one of the same module does not hold the node's own apply back: +// only the composition the module's want names, else its newest, counts (the review of issue 1945: the bucket keeps +// compositions written before the filled paths, which the SDK now refuses, and nobody applies them). +func TestAStaleRefusedCompositionDoesNotHoldTheNodesOwnBack(t *testing.T) { + b := newBed(t) + stale, err := delivery.ComposedKey(node, "power", fpOf("a")) + if err != nil { + t.Fatal(err) + } + b.comps.before(stale, []byte(`{"v":1,"not":"a composition"}`)) + gk, graw, _ := composition(t, "power", fpOf("b"), false, file("power", "x", "1")) + b.comps.before(gk, graw) + 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 node's own not applied", func() bool { return len(b.appliedList()) == 1 }) +}