diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index 2903a82..bd736c6 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -316,9 +316,11 @@ func Wants(m Module, n Nodes, wants map[string]delivery.Want, lastHealthy map[st ok, target = true, have.FP } } - if ok && target == m.FP && len(out.NodeFailed) > 0 && !(had && !before.Removed && before.FP == m.FP) { - // While a node fails F alone, F reaches no further node: a node whose want does not name F yet gets no new want - // naming it, and wants already written stay (ADR 0557 §2). + if ok && target == m.FP && len(out.NodeFailed) > 0 && !(had && !before.Removed && before.FP == m.FP) && + n.Haves[node][m.Name].FP != m.FP { + // While a node fails F alone, F reaches no further node: a node that neither wants nor runs F gets no new want + // naming it, and wants already written stay (ADR 0557 §2). A node that runs F with no live want gets its want at F, or + // it would be on neither list and its node-engine would give the module up (issue 1945). ok, rule, words = false, delivery.RuleNotHeld, FailedAloneWords(m.Name, m.FP, slices.Sorted(maps.Keys(out.NodeFailed))) } switch { diff --git a/modules/mesh-delivery-assigner/internal/assign/wants_test.go b/modules/mesh-delivery-assigner/internal/assign/wants_test.go index 3e6985b..ff629e2 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants_test.go @@ -183,6 +183,13 @@ func TestAFailedHealthHoldsAndSetsBack(t *testing.T) { if _, written := out.Wants["g14"]; written { t.Errorf("g14's want at F was rewritten: %+v", out.Wants["g14"]) } + // A node that runs F healthy with no want, while g14 fails it alone, gets its want at F and is on no neither list (issue 1945). + n.Haves["novox"]["mail"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + delete(before, "novox") + out = Wants(three, n, before, map[string]string{"ace": fOld, "g14": fOld, "novox": fOld}) + if w := out.Wants["novox"]; w.FP != fNew || len(out.Neither) != 0 { + t.Errorf("novox running F with no want: want %+v, neither %+v; want its want at F and no neither", w, out.Neither) + } n.Haves["ace"]["mail"], n.Haves["g14"]["mail"] = failedHave, n.Haves["ace"]["mail"] // ADR 0557 §2: a witness that is offline keeps the have it last wrote and witnesses nothing (D1); F failing on a second node is