From 1c66d58bce0d93901b34a99371cf8acda97d485a Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 3 Nov 2026 19:36:18 +0100 Subject: [PATCH] =?UTF-8?q?The=20assigner=20waits=20for=20a=20provision=20?= =?UTF-8?q?whose=20provider=20is=20not=20known=20yet:=20an=20unknown=20pro?= =?UTF-8?q?vider=20has=20provided=20nothing,=20so=20a=20consumer=20never?= =?UTF-8?q?=20moves=20ahead=20of=20a=20provider=20the=20indexer=20has=20no?= =?UTF-8?q?t=20taken=20in=20(ADR=200603=20=C2=A75);=20the=20integration=20?= =?UTF-8?q?test's=20write=20order=20is=20unordered=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mesh-delivery-assigner/integration_test.go | 9 +++------ .../internal/assign/wants.go | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index 7b456f16..cadba393 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -448,12 +448,9 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { toolDigest := b.manifest(t, "tool", `{"module":"tool","version":"1"}`) fpOf := func(c byte) string { return delivery.SourcePrefix2 + strings.Repeat(string(c), 64) } toolOld := fpOf('1') - // In this order: the provider's module is in the indexer's state before the consumer's, so the consumer's requirement - // has a provider to be judged against whenever the assigner looks at it (a map's order made it flaky, 5 of 40 runs). - for _, row := range []struct { - module, fp, digest string - }{{"db", fpOf('2'), dbDigest}, {"app", fpOf('3'), appDigest}, {"tool", fpOf('4'), toolDigest}} { - module := row.module + for module, row := range map[string]struct { + fp, digest string + }{"db": {fpOf('2'), dbDigest}, "app": {fpOf('3'), appDigest}, "tool": {fpOf('4'), toolDigest}} { mod, _ := delivery.EncodeModule(delivery.Module{FP2: row.fp, Manifest: row.digest, Repository: "novox/" + module, Commit: strings.Repeat("2", 40), Path: delivery.RootPath}) b.putJSON(t, delivery.ModulesBucket, module, mod) fps := []string{row.fp} diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index a7f19d62..56704d68 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -154,10 +154,26 @@ func (m Module) provisionWaiting(node string) (string, bool) { } keyed := m.Provisions(node) // Only a provision the newest manifest still requires is waited on (ADR 0603 §9): a key of an older revision's edge, - // asked or retiring for the operator's answer (ADR 0570 §5), holds nothing. + // asked or retiring for the operator's answer (ADR 0570 §5), holds nothing. A requirement catalogue.Awaited drops for a + // reason the decision keeps (optional, not keyed: a toolchain on the module's own machine, a login the controller makes) is + // not waited on; one it drops only because no answering provider is known yet is (ADR 0603 §5: an unknown provider has + // provided nothing), so a consumer never moves ahead of a provider whose module the indexer has not yet taken in. needed := m.Manifest.Requires if m.Answering != nil { needed = catalogue.Awaited(m.Manifest, m.Answering) + have := map[string]bool{} + for _, p := range keyed { + have[p.Provision] = true + } + for _, r := range slices.Sorted(slices.Values(m.Manifest.Requires)) { + if m.Manifest.OptionalRequires[r] || len(m.Answering(r)) > 0 || slices.Contains(needed, r) { + continue + } + needed = append(needed, r) + if !have[r] { + return fmt.Sprintf("no provider of %s is known yet for %s on %s: an unknown provider has provided nothing", r, m.Name, node), true + } + } } for _, key := range slices.Sorted(maps.Keys(keyed)) { if p := keyed[key]; p.State != provisions.Provided && slices.Contains(needed, p.Provision) {