At each mesh-grants restart and user-list reload (16:37:24, 16:55:15) nats-server 2.11's reloadAuthorization left the mesh's account without its imports, $JS.API.> among them, for a few seconds: requests had no responders, JetStream calls said it was not enabled, and a build-test result on ace was lost. Quick fix, chosen as asked; the thorough fix is a reload that keeps the account's imports, in the bus's holder. The package busretry asks a request again, backing off within about 30 s, only while its error says it reached no one (no responders, JetStream not enabled, no response from the stream, or either in words): nothing was done, so even a create or an update is safe to ask again. A timeout or any answer is never asked again. After the bound it is said on standard error and returned. Used by process.State's requests, process.Conn's calls (asked again only while $JS.API.INFO says the bus itself answers nothing, so a callee that is not running still fails at once) and asks, and the bundle's state requests through the runtime.
75 lines
2.9 KiB
Go
75 lines
2.9 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/busretry"
|
|
)
|
|
|
|
// Ask reads the router's record of one of this process's module's own asks, under its own module and no
|
|
// other's (novox/hq ADR 0436 clause 4, ADR 0259 §3): by direct get on bucket, the same subject the tool
|
|
// runner reads (mesh-tools node-tools/internal/bus/seat.go ~300-345):
|
|
//
|
|
// $JS.API.DIRECT.GET.KV_<bucket>.$KV.<bucket>.<module>.<id>
|
|
//
|
|
// bucket is the router's records bucket as the caller's own manifest names it (operator-channel's is
|
|
// messenger_asks in the controller's own test fixtures) — never hard-coded to one bucket here, since a
|
|
// process may read records of more than one seat's router. A missing, deleted or purged key is nil, not
|
|
// an error.
|
|
func (c *Conn) Ask(bucket, id string) (*stdio.StateEntry, error) {
|
|
if err := checkAskID(id); err != nil {
|
|
return nil, err
|
|
}
|
|
subject := "$JS.API.DIRECT.GET.KV_" + bucket + ".$KV." + bucket + "." + c.account.Module + "." + id
|
|
// A read: asked again while the bus answers nothing (busretry).
|
|
reply, err := busretry.Do("reading "+subject, func() (*nats.Msg, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), stateTimeout)
|
|
defer cancel()
|
|
return c.nc.RequestWithContext(ctx, subject, nil)
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading %s's ask %s from %s: %w", c.account.Module, id, bucket, err)
|
|
}
|
|
if status := reply.Header.Get("Status"); status == "404" {
|
|
return nil, nil
|
|
} else if status != "" {
|
|
return nil, fmt.Errorf("the record %s.%s could not be read: %s %s", c.account.Module, id, status,
|
|
reply.Header.Get("Description"))
|
|
}
|
|
if op := reply.Header.Get("KV-Operation"); op == "DEL" || op == "PURGE" {
|
|
return nil, nil
|
|
}
|
|
var revision uint64
|
|
if _, err := fmt.Sscan(reply.Header.Get("Nats-Sequence"), &revision); err != nil {
|
|
return nil, fmt.Errorf("the record %s.%s carries no readable revision: %w", c.account.Module, id, err)
|
|
}
|
|
return &stdio.StateEntry{Key: c.account.Module + "." + id, Value: askValueOf(reply.Data), Revision: revision}, nil
|
|
}
|
|
|
|
// checkAskID refuses an id holding a dot or a wildcard before anything is sent: either would name a
|
|
// different key under the direct-get subject's own dot-joined path, never this one (novox/hq issue 1189
|
|
// AC2).
|
|
func checkAskID(id string) error {
|
|
if id == "" || strings.ContainsAny(id, ".*>") {
|
|
return fmt.Errorf("%q is not an ask id: an ask id holds no dot and no wildcard, naming one key and "+
|
|
"no other", id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// askValueOf is a record's raw bytes as JSON, quoting them when they are not already valid JSON
|
|
// (mesh-tools node-tools/internal/bus/state.go:351-357, valueOf).
|
|
func askValueOf(raw []byte) json.RawMessage {
|
|
if len(raw) == 0 || !json.Valid(raw) {
|
|
b, _ := json.Marshal(string(raw))
|
|
return b
|
|
}
|
|
return json.RawMessage(append([]byte(nil), raw...))
|
|
}
|