From f007ca42279a77058d59ca3ef106a160a2cb1cc8 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 26 Oct 2026 22:34:25 +0100 Subject: [PATCH] =?UTF-8?q?A=20node=20that=20runs=20a=20version=20alone-fa?= =?UTF-8?q?iled=20elsewhere=20still=20gets=20its=20want=20at=20it=20(ADR?= =?UTF-8?q?=200557=20=C2=A72,=20issue=201496)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of 50c4674: the bar on new wants naming F while a node fails it alone also caught a node that runs F with no live want, the issue-1945 recovery path: it went on neither list and its node-engine gave the module up. The bar now holds only a node that neither wants nor runs F. --- modules/mesh-delivery-assigner/internal/assign/wants.go | 8 +++++--- .../mesh-delivery-assigner/internal/assign/wants_test.go | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) 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