Merge pull request 'Hold a holder's caller list from its config watch, so a timed-out ask refuses no verb (issue 1966; group with mesh-catalog, merge first)' (#182) from fix/1966-callers-config-read into main
This commit was merged in pull request #182.
This commit is contained in:
@@ -3,6 +3,7 @@ package stdio
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -183,3 +184,44 @@ func TestArgumentsOfNoCallHaveNoCaller(t *testing.T) {
|
|||||||
t.Fatalf("CallerOf(nil) answered %q", got)
|
t.Fatalf("CallerOf(nil) answered %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A holder's caller list is held (issue 1966): the runtime is asked once, by a watch; a later call never asks again, so
|
||||||
|
// an ask that timed out refuses no verb, and a change that cannot be read keeps the list last read.
|
||||||
|
func TestAHeldCallerListIsAskedOnceAndSurvivesAnUnreadableChange(t *testing.T) {
|
||||||
|
verbs, defaults := []string{"publish"}, VerbCallers{"publish": {"a"}}
|
||||||
|
var asks int
|
||||||
|
var push func(ProvisionChange) error
|
||||||
|
fail := true
|
||||||
|
read := holdVerbCallers(func(key string, h func(ProvisionChange) error) error {
|
||||||
|
asks++
|
||||||
|
if fail {
|
||||||
|
return errors.New("nats: timeout")
|
||||||
|
}
|
||||||
|
push = h
|
||||||
|
return h(ProvisionChange{Kind: "config", Key: key, Value: json.RawMessage(`{"publish":["b"]}`)})
|
||||||
|
}, "callers", verbs, defaults)
|
||||||
|
if _, err := read(); err == nil {
|
||||||
|
t.Fatal("a list never read was answered")
|
||||||
|
}
|
||||||
|
fail = false
|
||||||
|
c, err := read()
|
||||||
|
if err != nil || c.Allowed("publish", "ace.b") != nil || asks != 2 {
|
||||||
|
t.Fatalf("after the ask answered: %v, %v, asked %d times", c, err, asks)
|
||||||
|
}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
if _, err := read(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if asks != 2 {
|
||||||
|
t.Errorf("a held list was asked again: %d asks", asks)
|
||||||
|
}
|
||||||
|
_ = push(ProvisionChange{Kind: "config", Key: "callers", Value: json.RawMessage(`{"nothing":["x"]}`)})
|
||||||
|
if c, err := read(); err != nil || c.Allowed("publish", "ace.b") != nil {
|
||||||
|
t.Errorf("an unreadable change dropped the list last read: %v, %v", c, err)
|
||||||
|
}
|
||||||
|
_ = push(ProvisionChange{Kind: "config", Key: "callers", Value: json.RawMessage(`{"publish":["c"]}`)})
|
||||||
|
if c, _ := read(); c.Allowed("publish", "ace.c") != nil || c.Allowed("publish", "ace.b") == nil {
|
||||||
|
t.Errorf("a change was not held: %v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Callers is a module's trusted list of who may call a verb of its (novox/hq ADR 0459 §2, ADR 0563 §8), as the mesh
|
// Callers is a module's trusted list of who may call a verb of its (novox/hq ADR 0459 §2, ADR 0563 §8), as the mesh
|
||||||
@@ -122,6 +123,66 @@ func ConfiguredVerbCallers(key string, verbs []string, defaults VerbCallers) (Ve
|
|||||||
return parseConfiguredVerbCallers(raw, verbs, defaults)
|
return parseConfiguredVerbCallers(raw, verbs, defaults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HoldVerbCallers is ConfiguredVerbCallers held: the returned reader asks the runtime once, by a watch of the config
|
||||||
|
// value key (WatchConfig), and from then on answers the list it holds, which each change of the value replaces. A call
|
||||||
|
// never asks the runtime again, so a runtime that is slow or away for a moment (a timed-out ask) refuses no verb; a
|
||||||
|
// change that cannot be read keeps the list last read and is said on the bundle's standard error, so a broken list is
|
||||||
|
// said and never widened. Only a reader that has not yet read any value answers an error, and its next call asks again.
|
||||||
|
func HoldVerbCallers(key string, verbs []string, defaults VerbCallers) func() (VerbCallers, error) {
|
||||||
|
return holdVerbCallers(WatchConfig, key, verbs, defaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
func holdVerbCallers(watch func(string, func(ProvisionChange) error) error, key string, verbs []string, defaults VerbCallers) func() (VerbCallers, error) {
|
||||||
|
var start, mu sync.Mutex
|
||||||
|
var held VerbCallers
|
||||||
|
var bad error
|
||||||
|
handle := func(c ProvisionChange) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if c.Refused != "" {
|
||||||
|
bad = fmt.Errorf("the config value %s is not answered: %s", key, c.Refused)
|
||||||
|
} else if l, err := parseConfiguredVerbCallers(c.Value, verbs, defaults); err != nil {
|
||||||
|
bad = fmt.Errorf("the config value %s cannot be read: %w", key, err)
|
||||||
|
} else {
|
||||||
|
held, bad = l, nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if held != nil {
|
||||||
|
sayf("%v: the list last read stays", bad)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
read := func() (VerbCallers, error) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if held != nil {
|
||||||
|
return held, nil
|
||||||
|
}
|
||||||
|
if bad != nil {
|
||||||
|
return nil, bad
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return func() (VerbCallers, error) {
|
||||||
|
if l, err := read(); l != nil || err != nil {
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
start.Lock()
|
||||||
|
defer start.Unlock()
|
||||||
|
if l, err := read(); l != nil || err != nil {
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
if err := watch(key, handle); err != nil {
|
||||||
|
return nil, fmt.Errorf("the config value %s cannot be read: %w", key, err)
|
||||||
|
}
|
||||||
|
l, err := read()
|
||||||
|
if l == nil && err == nil {
|
||||||
|
err = fmt.Errorf("the config value %s cannot be read: the runtime handed over no value", key)
|
||||||
|
}
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseConfiguredVerbCallers(raw json.RawMessage, verbs []string, defaults VerbCallers) (VerbCallers, error) {
|
func parseConfiguredVerbCallers(raw json.RawMessage, verbs []string, defaults VerbCallers) (VerbCallers, error) {
|
||||||
var text string
|
var text string
|
||||||
if err := json.Unmarshal(raw, &text); err == nil || len(bytes.TrimSpace(raw)) == 0 || string(bytes.TrimSpace(raw)) == "null" {
|
if err := json.Unmarshal(raw, &text); err == nil || len(bytes.TrimSpace(raw)) == 0 || string(bytes.TrimSpace(raw)) == "null" {
|
||||||
|
|||||||
Reference in New Issue
Block a user