From 030bdc537e1d0beb71374811542c2bbda6b4e473 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 14:35:32 +0000 Subject: [PATCH] Read the router's ask ids without taking a second stop of the key lister for a failure (issue 1336) --- cmd/mesh-controller/catch_up.go | 10 +++- .../router_ids_integration_test.go | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 cmd/mesh-controller/router_ids_integration_test.go diff --git a/cmd/mesh-controller/catch_up.go b/cmd/mesh-controller/catch_up.go index b960e853..5cb80a9f 100644 --- a/cmd/mesh-controller/catch_up.go +++ b/cmd/mesh-controller/catch_up.go @@ -14,6 +14,7 @@ import ( "sync" "time" + "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" "git.novox.be/novox/mesh-sdk/go/asks" @@ -267,6 +268,11 @@ var routerAskIDs = func(ctx context.Context, inv catalogueReader, asker string) if err != nil { return nil, err } + return askIDsOf(ctx, kv, asker) +} + +// askIDsOf are the ids of the asks an asker has in the router's bucket. +func askIDsOf(ctx context.Context, kv jetstream.KeyValue, asker string) ([]string, error) { lister, err := kv.ListKeysFiltered(ctx, asker+".>") if err != nil { return nil, err @@ -275,7 +281,9 @@ var routerAskIDs = func(ctx context.Context, inv catalogueReader, asker string) for key := range lister.Keys() { ids = append(ids, strings.TrimPrefix(key, asker+".")) } - if err := lister.Stop(); err != nil { + // The lister stops its own watch when it has sent the last key, so a stop of ours finds it stopped: nats answers that with + // "invalid subscription", which is no failure of the read (issue 1336). Any other error stays one. + if err := lister.Stop(); err != nil && !errors.Is(err, nats.ErrBadSubscription) { return nil, err } return ids, nil diff --git a/cmd/mesh-controller/router_ids_integration_test.go b/cmd/mesh-controller/router_ids_integration_test.go new file mode 100644 index 00000000..2f238fe9 --- /dev/null +++ b/cmd/mesh-controller/router_ids_integration_test.go @@ -0,0 +1,52 @@ +//go:build integration + +package main + +import ( + "context" + "slices" + "testing" + + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" + + "github.com/novox/mesh-controller/internal/testbus" +) + +// Issue 1336: the catch-up lists the ids of an asker's asks in the router's bucket. The key lister stops its own watch when the last +// key is read, and a second stop answers "nats: invalid subscription"; read as a failure, it made every warrant unreadable on novox +// (2026-10-20, 110 gaps named, none planned). +func TestTheAskIDsOfAnAskerAreReadFromTheRoutersBucketWithoutAFalseFailure(t *testing.T) { + conn, err := nats.Connect(testbus.URL(t)) + if err != nil { + t.Fatal(err) + } + t.Cleanup(conn.Close) + js, err := jetstream.New(conn) + if err != nil { + t.Fatal(err) + } + ctx := t.Context() + kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "router_asks_test"}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = js.DeleteKeyValue(context.Background(), "router_asks_test") }) + for _, key := range []string{"mesh-delivery.merge-378-b9104454f9a5-1", "mesh-delivery.merge-377-aaaaaaaaaaaa-1", + "mesh-delivery-teller.merge-feat_r4b_sdk_users-ba59b03220f8-989afe0a3bcf", "keycloak.something"} { + if _, err := kv.Put(ctx, key, []byte(`{}`)); err != nil { + t.Fatal(err) + } + } + got, err := askIDsOf(ctx, kv, "mesh-delivery") + if err != nil { + t.Fatalf("the ids of an asker's asks could not be read: %v", err) + } + slices.Sort(got) + if want := []string{"merge-377-aaaaaaaaaaaa-1", "merge-378-b9104454f9a5-1"}; !slices.Equal(got, want) { + t.Fatalf("read %v, want %v: the teller's and another module's asks are not mesh-delivery's", got, want) + } + if none, err := askIDsOf(ctx, kv, "nobody"); err != nil || len(none) != 0 { + t.Fatalf("an asker with no asks read as %v, %v", none, err) + } +}