diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index 5967fbc2..9cac9179 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -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) } }) diff --git a/modules/mesh-grants/internal/buscompose/objects.go b/modules/mesh-grants/internal/buscompose/objects.go index 0d3e0af3..e064525f 100644 --- a/modules/mesh-grants/internal/buscompose/objects.go +++ b/modules/mesh-grants/internal/buscompose/objects.go @@ -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 { diff --git a/modules/mesh-grants/internal/grants/full.go b/modules/mesh-grants/internal/grants/full.go index 872a6498..f563887a 100644 --- a/modules/mesh-grants/internal/grants/full.go +++ b/modules/mesh-grants/internal/grants/full.go @@ -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 } } diff --git a/modules/mesh-grants/internal/grants/part.go b/modules/mesh-grants/internal/grants/part.go index ed644c8d..16b7dd41 100644 --- a/modules/mesh-grants/internal/grants/part.go +++ b/modules/mesh-grants/internal/grants/part.go @@ -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()