DLV-G3: the grants part raises no history-full; fill is the bus module's, and the old key is cleared once at start (ADR 0595 §6, design 25 § 12.3)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -703,8 +703,8 @@ func TestGrants(t *testing.T) {
|
||||
t.Errorf("a reader of a held module's object store lost its grant: %v", heldWeb.Publish)
|
||||
}
|
||||
b.manifestOf("mesh-knowledge", knowledge) // accepted again for the rest of the row
|
||||
// ADR 0592 §6, §25: the bucket past 75 percent of its cap is said, in one warning, and cleared once below; the history
|
||||
// stream at 10 percent is not.
|
||||
// ADR 0595 §6, design 25 § 12.3 (DLV-G3): fill is the bus module's; the part raises no history-full, however full a
|
||||
// bucket is, and clears the old key once after its start.
|
||||
var said []asks.Condition
|
||||
p.Say = func(c asks.Condition) error {
|
||||
if c.Key == grants.FullCondition {
|
||||
@@ -712,25 +712,15 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
size := func(stream string, bytes int64) {
|
||||
for _, s := range b.streams {
|
||||
if s["name"] == stream {
|
||||
s["bytes"] = bytes
|
||||
}
|
||||
for _, s := range b.streams {
|
||||
if s["name"] == "OBJ_mesh-knowledge_sections" {
|
||||
s["bytes"] = int64(3500 << 20)
|
||||
}
|
||||
}
|
||||
size("OBJ_mesh-knowledge_sections", 3500<<20)
|
||||
size("VERSIONS", 200<<20)
|
||||
if res := p.Compose(); len(res.Full) != 1 || res.Full[0].Stream != "OBJ_mesh-knowledge_sections" || res.Full[0].Percent() != 85 ||
|
||||
len(said) != 1 || said[0].State != asks.StateRaised || !strings.Contains(said[0].Summary, "3500 of 4096 MiB (85%)") {
|
||||
t.Fatalf("full = %+v, said %+v", res.Full, said)
|
||||
}
|
||||
if p.Compose(); len(said) != 1 {
|
||||
t.Errorf("the same fill was said again: %+v", said)
|
||||
}
|
||||
size("OBJ_mesh-knowledge_sections", 1<<20)
|
||||
if res := p.Compose(); len(res.Full) != 0 || len(said) != 2 || said[1].State != asks.StateCleared {
|
||||
t.Errorf("below its mark: full %+v, said %+v", res.Full, said)
|
||||
p.Compose()
|
||||
p.Compose()
|
||||
if len(said) != 1 || said[0].State != asks.StateCleared {
|
||||
t.Errorf("history-full: said %+v, want one clear and no raise", said)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ import (
|
||||
// A module's own object bucket (novox/hq ADR 0592 §5, §9). The grants part creates it once, create-only, kept with no
|
||||
// age limit and refusing a new object when full; only its module writes it; another module reads it only through a
|
||||
// `reads` entry naming it and the owner's trusted config value of the bucket's grant naming that module, read-only, as a
|
||||
// restricted stream is heard (ADR 0363 §4, §5). The condition when it passes 75 percent of its cap is the grants part's
|
||||
// (grants.FullCondition).
|
||||
// restricted stream is heard (ADR 0363 §4, §5). The condition when it fills is the bus module's (ADR 0595 §6).
|
||||
|
||||
// ObjectBucket is one object bucket a module owns, as the bus holds it.
|
||||
type ObjectBucket struct {
|
||||
|
||||
@@ -1,95 +1,32 @@
|
||||
package grants
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// **A history stream or an object bucket past 75 percent of its cap is said, in one place for every module** (novox/hq
|
||||
// ADR 0592 §6, §25; ADR 0593 §10 gives the controller's part to this part). Such a stream or bucket refuses a new message
|
||||
// or object when full rather than dropping its oldest, so filling up is the one way it fails: the part reads every
|
||||
// stream's size from the bus module's nats_streams at each pass (HeldStreams) and raises the warning FullCondition naming
|
||||
// each one past catalogue.FullWarnPercent, cleared once none is.
|
||||
// **The fill of a module's kept data is the bus module's** (novox/hq ADR 0595 §6, design 25 § 12.3): its one periodic
|
||||
// read raises `full` at its raise mark and clears it below its clear mark, for every entry that refuses when full. The
|
||||
// grants part no longer reads any stream's size and raises nothing of fill: `mesh-grants.history-full` moved to the bus
|
||||
// module, its new condition raised first, then this old key cleared. So the part clears the old key once, at its first
|
||||
// pass after a start (DLV-G3), whether or not one is held: a clear of a condition not raised is a no-op at the keeper.
|
||||
|
||||
// FullCondition is the key of the warning raised while a history stream or an object bucket is past its mark.
|
||||
// FullCondition is the key of the warning this part raised before ADR 0595 §6 moved fill to the bus module: only ever
|
||||
// cleared now.
|
||||
const FullCondition = "mesh-grants.history-full"
|
||||
|
||||
// Fill is how full one history stream or object bucket is: its module, its stream on the bus, its bytes and its cap.
|
||||
type Fill struct {
|
||||
Module string `json:"module"`
|
||||
Stream string `json:"stream"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
Cap int64 `json:"cap"`
|
||||
}
|
||||
|
||||
// Percent is its bytes in percent of its cap, rounded down.
|
||||
func (f Fill) Percent() int64 { return f.Bytes * 100 / f.Cap }
|
||||
|
||||
// Full is every history stream and object bucket of a placed module past catalogue.FullWarnPercent of its cap, by the
|
||||
// sizes the bus holds (bytes, by stream name), in module and stream order. One the bus does not hold is not full. Pure.
|
||||
func Full(in Inputs, bytes map[string]int64) []Fill {
|
||||
var out []Fill
|
||||
for _, module := range sortedKeys(in.Manifests) {
|
||||
if !assigned(in, module) {
|
||||
continue
|
||||
}
|
||||
m := in.Manifests[module]
|
||||
var capped []Fill
|
||||
for _, s := range m.Streams {
|
||||
if s.KeepsHistory() {
|
||||
capped = append(capped, Fill{Module: module, Stream: s.BusName(), Cap: int64(s.Cap()) << 20})
|
||||
}
|
||||
}
|
||||
for _, o := range objectsOf(m) {
|
||||
capped = append(capped, Fill{Module: module, Stream: "OBJ_" + o.Bucket(), Cap: int64(o.MaxMiB) << 20})
|
||||
}
|
||||
for _, f := range capped {
|
||||
held, ok := bytes[f.Stream]
|
||||
if !ok || f.Cap <= 0 {
|
||||
continue
|
||||
}
|
||||
f.Bytes = held
|
||||
if f.Bytes*100 >= catalogue.FullWarnPercent*f.Cap {
|
||||
out = append(out, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// sayFull raises FullCondition while a history stream or an object bucket is past its mark, and clears it once none is:
|
||||
// said once per set of streams, again when that set changes, and at the first pass after a start.
|
||||
func (p *Part) sayFull(full []Fill) {
|
||||
// clearFull clears FullCondition once after a start; a clear refused is tried again at the next pass.
|
||||
func (p *Part) clearFull() {
|
||||
if p.Say == nil {
|
||||
return
|
||||
}
|
||||
var names, lines []string
|
||||
for _, f := range full {
|
||||
names = append(names, f.Stream)
|
||||
lines = append(lines, fmt.Sprintf("%s of %s: %d of %d MiB (%d%%)", f.Stream, f.Module, f.Bytes>>20, f.Cap>>20, f.Percent()))
|
||||
}
|
||||
said := strings.Join(names, " ")
|
||||
p.holdMu.Lock()
|
||||
defer p.holdMu.Unlock()
|
||||
if p.fullKnown && said == p.fullSaid {
|
||||
if p.fullCleared {
|
||||
return
|
||||
}
|
||||
c := asks.Condition{Key: FullCondition, Subject: asks.Scope{Scope: "module", ID: Module}}
|
||||
if said == "" {
|
||||
c.State = asks.StateCleared
|
||||
} else {
|
||||
c.Severity, c.State = asks.SeverityWarning, asks.StateRaised
|
||||
c.Summary = fmt.Sprintf("%d history stream(s) or object bucket(s) past %d%% of their cap: %s", len(full),
|
||||
catalogue.FullWarnPercent, strings.Join(lines, "; "))
|
||||
c.Headline, c.Needs = "A stream that keeps its history is filling up", "a larger cap in the module's manifest, or data removed"
|
||||
c.Explanation = asks.NeedsYou + "a decision before it is full: such a stream or bucket refuses new messages when full " +
|
||||
"rather than dropping its oldest (novox/hq ADR 0592 §6). " + strings.Join(lines, "; ")
|
||||
c.Resolved = "every history stream and object bucket is below its mark"
|
||||
}
|
||||
c := asks.Condition{Key: FullCondition, Subject: asks.Scope{Scope: "module", ID: Module}, State: asks.StateCleared,
|
||||
Resolved: "the fill of a module's kept data is the bus module's own condition now (novox/hq ADR 0595 §6)"}
|
||||
if p.Say(c) == nil {
|
||||
p.fullSaid, p.fullKnown = said, true
|
||||
p.fullCleared = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,6 @@ func (s UserListState) inputs() string {
|
||||
|
||||
// Result is what one composition did.
|
||||
type Result struct {
|
||||
// Full is every history stream and object bucket past its mark at this pass (full.go, ADR 0592 §6).
|
||||
Full []Fill `json:"full,omitempty"`
|
||||
UserList UserListState `json:"user_list"`
|
||||
Revision uint64 `json:"revision"`
|
||||
Written bool `json:"written"`
|
||||
@@ -183,9 +181,8 @@ type Part struct {
|
||||
behindSaid string
|
||||
behindKnown bool
|
||||
behindSeen map[string]bool
|
||||
// fullSaid is the history streams and object buckets past their mark last said as a condition, once fullKnown (full.go).
|
||||
fullSaid string
|
||||
fullKnown bool
|
||||
// fullCleared is whether the old history-full key was cleared since the start (full.go, ADR 0595 §6).
|
||||
fullCleared bool
|
||||
// refusedSaid is the refused principals last said as a condition, once refusedKnown (refused.go).
|
||||
refusedSaid string
|
||||
refusedKnown bool
|
||||
@@ -380,11 +377,8 @@ func (p *Part) Compose() Result {
|
||||
res.Retired[key] = m.Retired
|
||||
}
|
||||
}
|
||||
held, sizes, heldErr := HeldStreamsAndBytes(p.Call)
|
||||
if heldErr == nil {
|
||||
res.Full = Full(in, sizes)
|
||||
p.sayFull(res.Full)
|
||||
}
|
||||
p.clearFull()
|
||||
held, heldErr := HeldStreams(p.Call)
|
||||
var ask Ask
|
||||
if p.Create != nil || p.Consumers != nil {
|
||||
ask = p.asked()
|
||||
|
||||
Reference in New Issue
Block a user