The view's process hears wait-held and wait-ended of every part (seats and modules; waits.go, manifest consumes) and keeps them in
its state waits under owner.step.node.module (viewstate/waits.go); view_tiers reads them through the tools bundle and names each
module's waits in the sayer's own words, {owner, step, node, on, bound, since, words} (tiers.go waitsHeld), beside the indexer's
pending revisions as the seat mesh-catalog's step take-in. No state of another module is read for a tier's waits.
Builds: design 47 G §512, §376, §498, A-242
289 lines
11 KiB
Go
289 lines
11 KiB
Go
package view
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
|
)
|
|
|
|
// The tiers of a source revision are a view, worked out at each call and never stored (novox/hq ADR 0603 §1, §6): the
|
|
// modules the revision moves, ordered by the module graph's ordering edges among them (a base, a provision or bus state
|
|
// awaited from another of them, a verb another of them serves whose edge the graph keeps, ADR 0614 §1), and for each module
|
|
// what it waits on now: its build key, each node's want (named by its edge: a verb, a reader capability) and have, and
|
|
// each provision it awaits on each node, with the rule and the words of the part that holds it, and the verb edges the
|
|
// graph leaves out. Nothing in the mesh waits on
|
|
// a tier; the tiers say where the revision stands. A node the watchman says is offline is named away and left out of
|
|
// what the revision waits on (ADR 0603 §7).
|
|
|
|
// TiersTool is the view's read tool of a source revision's tiers.
|
|
const TiersTool = "view_tiers"
|
|
|
|
// TiersMost is the most modules view_tiers answers (ADR 0603 §2); past it the answer says how many it left out.
|
|
const TiersMost = 500
|
|
|
|
// Tiers is a source revision's tiers: Revision the source revision (the gate's source key's revision), Modules how many
|
|
// modules it moves (each whose fingerprint last changed at it, meshcatalog.ModulesByModuleValue.FPSrc), Tiers those modules in order, the
|
|
// first tier waiting on none of the others. Cut says when the answer holds fewer than Modules, Graph why the module graph
|
|
// could not be read (then every module is in the first tier), Cycle a cycle among them (its modules form the last
|
|
// tier), and Away the nodes left out.
|
|
type Tiers struct {
|
|
// Branch is the watched branch whose newest source revision it is, when view_tiers was given no revision (A-173).
|
|
Branch string `json:"branch,omitempty"`
|
|
Revision uint64 `json:"revision"`
|
|
Modules int `json:"modules"`
|
|
Tiers [][]TierModule `json:"tiers"`
|
|
Cut string `json:"cut,omitempty"`
|
|
Graph string `json:"graph,omitempty"`
|
|
// Pending says why the indexer's waits could not be read, so no module is named held by its revision's indexer (ADR 0633 §5).
|
|
Pending string `json:"pending,omitempty"`
|
|
}
|
|
|
|
// TierModule is one module of a tier (design 47 G §512): its build (the fingerprint the difference says it is to run), the
|
|
// modules it is ordered after with the edge's kind, and each wait of its own now, from the parts' wait-held and the indexer's
|
|
// pending revisions (no wait is a module that reached every node present).
|
|
type TierModule struct {
|
|
Module string `json:"module"`
|
|
// FP is the build.
|
|
FP string `json:"build,omitempty"`
|
|
After []TierAfter `json:"after,omitempty"`
|
|
Waits []TierWait `json:"waits,omitempty"`
|
|
}
|
|
|
|
// TierAfter is a module a module is ordered after, with the kind of the edge that orders it (design 47 G §512, §378).
|
|
type TierAfter struct {
|
|
Module string `json:"module"`
|
|
Kind string `json:"kind"`
|
|
}
|
|
|
|
// TierWait is one wait of a module, as the part that holds it said it (design 47 G §512, §498): the seat or module that holds it
|
|
// (Owner), its step, the node it is on, what it waits on, its bound, since when, and its own words. The pending revision of the
|
|
// indexer is Owner mesh-catalog and Step take-in.
|
|
type TierWait struct {
|
|
Owner string `json:"owner"`
|
|
Step string `json:"step"`
|
|
Node string `json:"node,omitempty"`
|
|
On string `json:"on,omitempty"`
|
|
Bound string `json:"bound,omitempty"`
|
|
Since time.Time `json:"since,omitzero"`
|
|
Words string `json:"words,omitempty"`
|
|
}
|
|
|
|
// BranchTiers is view_tiers' answer given no revision: the tiers of each watched branch's newest source revision, in branch
|
|
// order (design 47 G A-173).
|
|
type BranchTiers struct {
|
|
Branches []Tiers `json:"branches"`
|
|
}
|
|
|
|
// Newest works out the tiers of each watched branch's newest source revision, as the gate's verb source answers it.
|
|
func (r Reader) Newest() (BranchTiers, error) {
|
|
if r.Sources == nil {
|
|
return BranchTiers{}, fmt.Errorf("the gate's source cannot be read here: name the source revision")
|
|
}
|
|
revs, err := r.Sources()
|
|
if err != nil {
|
|
return BranchTiers{}, fmt.Errorf("the gate's source: %w", err)
|
|
}
|
|
out := BranchTiers{Branches: []Tiers{}}
|
|
for _, branch := range slices.Sorted(maps.Keys(revs)) {
|
|
if revs[branch] == 0 {
|
|
continue
|
|
}
|
|
t, err := r.Tiers(revs[branch])
|
|
if err != nil {
|
|
return BranchTiers{}, fmt.Errorf("%s: %w", branch, err)
|
|
}
|
|
t.Branch = branch
|
|
out.Branches = append(out.Branches, t)
|
|
}
|
|
if len(out.Branches) == 0 {
|
|
return BranchTiers{}, fmt.Errorf("the gate holds no source revision of any watched branch yet")
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Tiers works out the tiers of one source revision (not 0): the difference of the kept assignment revision that carries it
|
|
// against the one kept before it, as the assigner's verb difference answers it with its tiers and each change's after
|
|
// (design 47 G §512, §186, A-242). The view orders nothing itself.
|
|
func (r Reader) Tiers(revision uint64) (Tiers, error) {
|
|
if revision == 0 {
|
|
return Tiers{}, fmt.Errorf("name the source revision")
|
|
}
|
|
out := Tiers{Revision: revision}
|
|
from, to, err := r.revisionsOf(revision)
|
|
if err != nil {
|
|
return Tiers{}, err
|
|
}
|
|
var d meshwants.Difference
|
|
if from > 0 {
|
|
if d, err = r.Difference(from, to); err != nil {
|
|
return Tiers{}, fmt.Errorf("the assigner's difference of %d and %d: %w", from, to, err)
|
|
}
|
|
} else {
|
|
out.Graph = fmt.Sprintf("no assignment revision is kept before %d, so the difference names nothing", to)
|
|
}
|
|
// One module per name, in the highest tier any of its nodes is in; the changes name each module's build and what it is
|
|
// ordered after.
|
|
changes := map[string][]meshwants.Change{}
|
|
for _, c := range d.Changes {
|
|
changes[c.Module] = append(changes[c.Module], c)
|
|
}
|
|
tierOf := map[string]int{}
|
|
for i, tier := range d.Tiers {
|
|
for _, ref := range tier {
|
|
tierOf[ref.Module] = max(tierOf[ref.Module], i)
|
|
}
|
|
}
|
|
names := slices.Sorted(maps.Keys(changes))
|
|
out.Modules = len(names)
|
|
if len(names) > TiersMost {
|
|
out.Cut = fmt.Sprintf("the revision moves %d modules: the first %d by name are shown, %s and the %d after it are not",
|
|
len(names), TiersMost, names[TiersMost], len(names)-TiersMost-1)
|
|
names = names[:TiersMost]
|
|
}
|
|
waits := r.waitsHeld(&out)
|
|
held, heldAll := r.heldBack(&out)
|
|
in := map[string]bool{}
|
|
for _, n := range names {
|
|
in[n] = true
|
|
}
|
|
for _, n := range names {
|
|
t := tierOf[n]
|
|
for len(out.Tiers) <= t {
|
|
out.Tiers = append(out.Tiers, nil)
|
|
}
|
|
tm := TierModule{Module: n}
|
|
seen := map[string]bool{}
|
|
for _, c := range changes[n] {
|
|
if tm.FP == "" {
|
|
tm.FP = c.To
|
|
}
|
|
for _, a := range c.After {
|
|
if k := a.Module + "\x00" + a.Kind; !seen[k] && a.Module != n {
|
|
seen[k] = true
|
|
tm.After = append(tm.After, TierAfter{Module: a.Module, Kind: a.Kind})
|
|
}
|
|
}
|
|
}
|
|
slices.SortFunc(tm.After, func(a, b TierAfter) int { return strings.Compare(a.Module+"\x00"+a.Kind, b.Module+"\x00"+b.Kind) })
|
|
tm.Waits = waits[n]
|
|
if w, ok := held[n]; ok {
|
|
tm.Waits = append(tm.Waits, w)
|
|
}
|
|
out.Tiers[t] = append(out.Tiers[t], tm)
|
|
}
|
|
// A module the indexer holds back is not at the revision yet, so no tier of it names it: it is named in the first tier with
|
|
// its wait alone (ADR 0633 §5).
|
|
for _, n := range heldAll {
|
|
if in[n] {
|
|
continue
|
|
}
|
|
if len(out.Tiers) == 0 {
|
|
out.Tiers = append(out.Tiers, nil)
|
|
}
|
|
out.Tiers[0] = append(out.Tiers[0], TierModule{Module: n, Waits: []TierWait{held[n]}})
|
|
}
|
|
compact := out.Tiers[:0]
|
|
for _, t := range out.Tiers {
|
|
if len(t) > 0 {
|
|
compact = append(compact, t)
|
|
}
|
|
}
|
|
out.Tiers = compact
|
|
return out, nil
|
|
}
|
|
|
|
// TakeInStep and TakeInOwner are the step and the owner of a wait on the indexer a source revision or a group is held for
|
|
// (ADR 0633 §5, design 47 G §512): the pending revision's wait is the seat mesh-catalog's step take-in.
|
|
const (
|
|
TakeInStep = "take-in"
|
|
TakeInOwner = "mesh-catalog"
|
|
)
|
|
|
|
// heldBack are the modules the indexer holds back, by module, each with its wait, read through the indexer's tool
|
|
// indexer_pending (ADR 0633 §5), and the same modules sorted. A tool that cannot be asked says so in out.Pending.
|
|
func (r Reader) heldBack(out *Tiers) (map[string]TierWait, []string) {
|
|
held := map[string]TierWait{}
|
|
if r.Pending == nil {
|
|
return held, nil
|
|
}
|
|
a, err := r.Pending()
|
|
if err != nil {
|
|
out.Pending = "the indexer's pending revisions cannot be read, so no module is named waiting for its revision's indexer: " + err.Error()
|
|
return held, nil
|
|
}
|
|
for _, w := range a.Waits {
|
|
words := "the indexer holds back " + w.Key + ", which waits for the indexer build " + w.WaitsFor
|
|
if w.What == takein.TakeInGroup {
|
|
words = "the indexer gathers the group " + w.Group + " and its end has not come"
|
|
} else if w.Behind != "" {
|
|
words = "the indexer holds back " + w.Key + " behind " + w.Behind
|
|
}
|
|
for _, module := range w.Holds {
|
|
if _, ok := held[module]; !ok { // the oldest wait names it
|
|
held[module] = TierWait{Owner: TakeInOwner, Step: TakeInStep, Words: words, Since: w.Since}
|
|
}
|
|
}
|
|
}
|
|
return held, slices.Sorted(maps.Keys(held))
|
|
}
|
|
|
|
// waitsHeld are the waits the parts hold, by module, from the view's own state waits (written from their wait-held and wait-ended,
|
|
// design 47 G §512, §376, A-242): each in the words its sayer gave. A state that cannot be read says so in out.Pending.
|
|
func (r Reader) waitsHeld(out *Tiers) map[string][]TierWait {
|
|
held := map[string][]TierWait{}
|
|
if r.WaitsState.Keys == nil || r.WaitsState.Get == nil {
|
|
return held
|
|
}
|
|
all, err := values(r.WaitsState)
|
|
if err != nil {
|
|
out.Pending += "the parts' waits cannot be read, so no module is named waiting on a part: " + err.Error()
|
|
return held
|
|
}
|
|
for _, key := range slices.Sorted(maps.Keys(all)) {
|
|
w, err := viewstate.ReadWait("", all[key])
|
|
if err != nil || w.Module == "" {
|
|
continue
|
|
}
|
|
held[w.Module] = append(held[w.Module], TierWait{Owner: w.Owner, Step: w.Step, Node: w.Node, On: w.On, Bound: w.Bound, Since: w.Since, Words: w.Words})
|
|
}
|
|
return held
|
|
}
|
|
|
|
// tiersRevision reads the tool's revision argument: a number, as JSON gives it or as text; 0 for the gate's newest.
|
|
func tiersRevision(v any) (uint64, error) {
|
|
switch x := v.(type) {
|
|
case nil:
|
|
return 0, nil
|
|
case float64:
|
|
if x < 0 || x != float64(uint64(x)) {
|
|
return 0, fmt.Errorf("the revision %v is no source revision", x)
|
|
}
|
|
return uint64(x), nil
|
|
case string:
|
|
if strings.TrimSpace(x) == "" {
|
|
return 0, nil
|
|
}
|
|
return strconv.ParseUint(strings.TrimSpace(x), 10, 64)
|
|
}
|
|
return 0, fmt.Errorf("the revision %v is no source revision", v)
|
|
}
|
|
|
|
// TiersOf is view_tiers' answer for its arguments.
|
|
func (r Reader) TiersOf(args map[string]any) (any, error) {
|
|
revision, err := tiersRevision(args["revision"])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if revision == 0 {
|
|
return r.Newest()
|
|
}
|
|
return r.Tiers(revision)
|
|
}
|