Merge pull request 'Say "nothing serves" only when the bus finds no subscriber, never for a tool's own error that mentions 503 (issue 1529)' (#39) from fix/1529-nothing-serves-only-for-no-responders into main

This commit was merged in pull request #39.
This commit is contained in:
2026-10-20 22:15:35 +00:00
3 changed files with 37 additions and 3 deletions
+5 -1
View File
@@ -708,12 +708,16 @@ var HandoverPause = 3 * time.Second
// (mesh-controller internal/link, `"retry": "handing-over"`).
const RetryHandingOver = "handing-over"
// ErrNoResponders is the bus's own refusal: nothing subscribes to the subject asked. It is told apart
// from a tool's own error by errors.Is, never by its text (a tool may say "503" itself, issue 1529).
var ErrNoResponders = errors.New("503 no responders")
// askOnce asks once, and says whether the answer was a refusal marked to be asked again.
func (c *Conn) askOnce(subject string, data []byte) (Answered, bool, error) {
msg, err := c.nc.Request(subject, data, RequestTimeout)
if err != nil {
if errors.Is(err, nats.ErrNoResponders) {
return Answered{}, false, errors.New("503 no responders")
return Answered{}, false, ErrNoResponders
}
if errors.Is(err, nats.ErrTimeout) {
return Answered{}, false, errors.New("timeout")
+4 -2
View File
@@ -7,6 +7,8 @@ import (
"regexp"
"strings"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-tools/node-tools/internal/bus"
)
@@ -109,7 +111,6 @@ func subjectListed(name, node string, l *Listing) string {
}
var (
noResponders = regexp.MustCompile(`(?i)no responders|503`)
// The bus's own two refusals, by their whole phrase. "authorization" alone also matched a tool's own
// answer that merely mentions the word — the runtime refusing a state value with an Authorization
// header (novox/hq ADR 0201) read as "this account may not call", which sent the reader the wrong way.
@@ -127,7 +128,8 @@ func whyItFailed(key string, err error) string {
}
msg := err.Error()
switch {
case noResponders.MatchString(msg):
// By type, not text: a tool's own error that mentions 503 is the tool's answer (issue 1529).
case errors.Is(err, bus.ErrNoResponders) || errors.Is(err, nats.ErrNoResponders):
extra := ""
if strings.HasPrefix(key, "seat:") {
extra = ", or nothing holds that seat"
@@ -0,0 +1,28 @@
package console
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-tools/node-tools/internal/bus"
)
// A tool's own error that mentions 503 is the tool's answer, not a routing fault (issue 1529).
func TestAToolsOwn503IsNotNothingServes(t *testing.T) {
got := whyItFailed("scanner.scan", errors.New("the scanner refused the scan job: 503 Service Unavailable"))
if strings.Contains(got, "nothing serves") || !strings.HasPrefix(got, "scanner.scan failed: ") || !strings.Contains(got, "503") {
t.Fatalf("got %q", got)
}
}
func TestARealNoRespondersStillSaysNothingServes(t *testing.T) {
for _, err := range []error{bus.ErrNoResponders, fmt.Errorf("ask: %w", bus.ErrNoResponders), nats.ErrNoResponders} {
if got := whyItFailed("a.b", err); !strings.HasPrefix(got, "nothing serves a.b") {
t.Fatalf("%v: got %q", err, got)
}
}
}