Read the router's ask ids without taking a second stop of the key lister for a failure (issue 1336)

This commit is contained in:
2026-10-20 14:35:32 +00:00
parent d163304e3f
commit 030bdc537e
2 changed files with 61 additions and 1 deletions
+9 -1
View File
@@ -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
@@ -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)
}
}