The assigner's state and verb placement are assignments, with its events, tools, views and every caller; the assigner copies the retired state at its start (issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -25,7 +25,7 @@ func holdsTool(read stdio.KeptState, ask asker, now func() time.Time, acts meshh
|
||||
") which not every counted node-engine states, present or away (ADR 0624 §1). Per module: what it needs, which nodes " +
|
||||
"lack what (or why no node is counted yet), the manifest the modules state keeps and the one held, the merge that " +
|
||||
"brought it and since when. The hold moves by itself once every counted node-engine states it (on manifest-reads-changed, " +
|
||||
"the placement's change or a merge). With module and ask_again, the engines of the nodes that lack it are asked again " +
|
||||
"the assignments's change or a merge). With module and ask_again, the engines of the nodes that lack it are asked again " +
|
||||
"and the hold is weighed now; with module, release and why, it moves now by your word whatever the engines state (a hand " +
|
||||
"act, recorded through mesh-hand-acts, which names the nodes it strands: each node whose engine lacks the reading, an " +
|
||||
"away node from its return, reads the new entry without it and an engine before form:seat-reach finds no store). A hold " +
|
||||
|
||||
@@ -29,7 +29,7 @@ func viewTools(getenv func(string) string, state func(string) views.Source, now
|
||||
for _, t := range index.ReadTools {
|
||||
out = append(out, views.ReadTool(index.Module, t.Tool, t.State, t.Words, state(t.State), marks, now))
|
||||
}
|
||||
// Its own first views, of the assigner's placement (DLV-F3r): read, and asked again by hand.
|
||||
// Its own first views, of the assigner's assignments (DLV-F3r): read, and asked again by hand.
|
||||
return append(out, views.StatusTools(index.Module, "indexer_first_views", "indexer_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
@@ -559,7 +559,7 @@ func startIndexerRun(t *testing.T, put func(context.Context, buildLog), before .
|
||||
b(ctx, js)
|
||||
}
|
||||
|
||||
serveAssignerPlacement(t, ctx, js, ctl)
|
||||
serveAssignerAssignments(t, ctx, js, ctl)
|
||||
|
||||
broker := filepath.Join(t.TempDir(), "broker")
|
||||
cred, _ := json.Marshal(process.Account{URL: srv.ClientURL(), Node: "n1", Module: "mesh-delivery-indexer", User: "n1.mesh-delivery-indexer", Password: "pw"})
|
||||
@@ -978,33 +978,33 @@ func TestIndexerJudgesAnUnreadableVerdictAgainAtStart(t *testing.T) { // 1923/AC
|
||||
// TestIndexerCountsOnlyThePlacedBuildNodesToolchains (issue 1940, issue 774): the toolchains bucket holds the report of
|
||||
// n2, where the catalogue no longer places mesh-build-go, at another version than n1's, as ace's v26.12.0 stayed beside
|
||||
// the v26.12.1 of the nodes mesh-build-node runs on. The indexer gives the merged module a fingerprint from n1's
|
||||
// toolchain alone; when the placement puts mesh-build-go on n2 again, its report written since counts and the builders
|
||||
// toolchain alone; when the assignments puts mesh-build-go on n2 again, its report written since counts and the builders
|
||||
// disagree (the guard stays); when it is taken off again, the fingerprint comes back. No restart, no timer: the
|
||||
// placement's watch (ADR 0549 §3), not the controller's assignments.
|
||||
// assignments's watch (ADR 0549 §3), not the controller's assignments.
|
||||
func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
||||
manifest, err := os.ReadFile("../../internal/index/testdata/catalogue-a67f0196-avahi.module.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const goSum = "3b5028f0c1e9bde04dd766abd63eb5931bc516bae695cf46f00abcd8510b0381"
|
||||
var placement, toolchains jetstream.KeyValue
|
||||
var assignments, toolchains jetstream.KeyValue
|
||||
place := func(ctx context.Context, t *testing.T, node string, modules ...string) {
|
||||
p := meshmoduleassigner.Placement{Node: node}
|
||||
p := meshmoduleassigner.Assignments{Node: node}
|
||||
for _, m := range modules {
|
||||
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
||||
}
|
||||
// Its node key, the commit that names the modules placed (research 072 row 37).
|
||||
values, err := meshmoduleassignerclient.PlacementValues(p)
|
||||
values, err := meshmoduleassignerclient.AssignmentsValues(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key := meshmoduleassigner.PlacementByNodeNodeKey(node)
|
||||
if _, err := placement.Put(ctx, key, values[key]); err != nil {
|
||||
key := meshmoduleassigner.AssignmentsByNodeNodeKey(node)
|
||||
if _, err := assignments.Put(ctx, key, values[key]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// DLV-F5r (ADR 0621 §3): the assigner's runtime starts together with the indexer and answers its first view only after
|
||||
// the indexer asked; within the bound the indexer follows the placement it answers (only n1's toolchain counts).
|
||||
// the indexer asked; within the bound the indexer follows the assignments it answers (only n1's toolchain counts).
|
||||
up := make(chan struct{})
|
||||
assignerLate = up
|
||||
t.Cleanup(func() { assignerLate = nil })
|
||||
@@ -1024,8 +1024,8 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// The seat mesh-placement's placement, as the assigner writes it (ADR 0567): n1 places mesh-build-go, n2 nothing.
|
||||
if placement, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.PlacementBucket, History: index.PlacementHistory}); err != nil {
|
||||
// The seat mesh-module-assigner's assignments, as the assigner writes it (ADR 0567): n1 places mesh-build-go, n2 nothing.
|
||||
if assignments, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.AssignmentsBucket, History: index.AssignmentsHistory}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
place(ctx, t, "n1", "mesh-build-go")
|
||||
@@ -1072,13 +1072,13 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
||||
if _, err := toolchains.Put(ctx, key, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// n2's report, written since its placement, counts: the builders disagree. A fingerprint already computed is kept
|
||||
// n2's report, written since its assignments, counts: the builders disagree. A fingerprint already computed is kept
|
||||
// (issue 1940), and only a new one needs the agreement: a merge moving avahi gets none until they agree.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if e, err := modules.Get(ctx, "avahi"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m, err := meshcatalogclient.DecodeModule(e.Value()); err != nil || m.Fp2 != held.Fp2 {
|
||||
t.Fatalf("after n2's placement avahi has fp2 %q, none2 %q: want its fingerprint %q kept", m.Fp2, m.None2, held.Fp2)
|
||||
t.Fatalf("after n2's assignments avahi has fp2 %q, none2 %q: want its fingerprint %q kept", m.Fp2, m.None2, held.Fp2)
|
||||
}
|
||||
moved := strings.Replace(string(data), `"newtree"`, `"movedtree"`, 1)
|
||||
if moved == string(data) {
|
||||
@@ -1091,11 +1091,11 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
||||
return m.Fp2 == "" && strings.HasPrefix(m.None2, "builders disagree: go1.27.2 recipe 1 on n2")
|
||||
}
|
||||
if m := read(disagree); !disagree(m) {
|
||||
t.Fatalf("within 20 s of a merge after n2's placement avahi has fp2 %q, none2 %q: want the builders to disagree", m.Fp2, m.None2)
|
||||
t.Fatalf("within 20 s of a merge after n2's assignments avahi has fp2 %q, none2 %q: want the builders to disagree", m.Fp2, m.None2)
|
||||
}
|
||||
place(ctx, t, "n2")
|
||||
if m := read(agreed); !agreed(m) {
|
||||
t.Fatalf("within 20 s of n2's placement taken away avahi has fp2 %q, none2 %q", m.Fp2, m.None2)
|
||||
t.Fatalf("within 20 s of n2's assignments taken away avahi has fp2 %q, none2 %q", m.Fp2, m.None2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1103,14 +1103,14 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
||||
// assigner's runtime started together with the indexer's, answering after the indexer asked (DLV-F5r).
|
||||
var assignerLate chan struct{}
|
||||
|
||||
// serveAssignerPlacement is the assigner as the owner of its placement (ADR 0615, ADR 0621), with the real owner's side:
|
||||
// its first-view tool placement answered from the bucket at its mark (the bucket made empty where a test made none),
|
||||
// serveAssignerAssignments is the assigner as the owner of its assignments (ADR 0615, ADR 0621), with the real owner's side:
|
||||
// its first-view tool assignments answered from the bucket at its mark (the bucket made empty where a test made none),
|
||||
// and its views family said by the views emitter onto ASSIGNER_VIEWS, which the indexer's durable consumer reads.
|
||||
func serveAssignerPlacement(t *testing.T, ctx context.Context, js jetstream.JetStream, nc *nats.Conn) {
|
||||
func serveAssignerAssignments(t *testing.T, ctx context.Context, js jetstream.JetStream, nc *nats.Conn) {
|
||||
t.Helper()
|
||||
placement, err := js.KeyValue(ctx, index.PlacementBucket)
|
||||
assignments, err := js.KeyValue(ctx, index.AssignmentsBucket)
|
||||
if err != nil {
|
||||
if placement, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.PlacementBucket, History: index.PlacementHistory}); err != nil {
|
||||
if assignments, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.AssignmentsBucket, History: index.AssignmentsHistory}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1124,11 +1124,11 @@ func serveAssignerPlacement(t *testing.T, ctx context.Context, js jetstream.JetS
|
||||
}
|
||||
em := views.Emitter{Owner: index.AssignerModule, Subject: process.Account{Node: "n1", Module: index.AssignerModule}.EventSubject,
|
||||
Publish: views.JetStreamPublish(js), Marks: marks}
|
||||
if err := em.Start(ctx, index.PlacementState, placement, nil); err != nil {
|
||||
if err := em.Start(ctx, index.AssignmentsState, assignments, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
late := assignerLate
|
||||
sub, err := nc.Subscribe("mesh.mod.mesh-delivery-assigner.call.placement.>", func(m *nats.Msg) {
|
||||
sub, err := nc.Subscribe("mesh.mod.mesh-delivery-assigner.call.assignments.>", func(m *nats.Msg) {
|
||||
if late != nil { // its runtime comes up after the indexer started (DLV-F5r)
|
||||
select {
|
||||
case <-late:
|
||||
@@ -1136,7 +1136,7 @@ func serveAssignerPlacement(t *testing.T, ctx context.Context, js jetstream.JetS
|
||||
return
|
||||
}
|
||||
}
|
||||
a, err := views.FirstView(index.AssignerModule, index.PlacementState, jsSource{ctx, placement}, jsSource{ctx, marks}, "", time.Now())
|
||||
a, err := views.FirstView(index.AssignerModule, index.AssignmentsState, jsSource{ctx, assignments}, jsSource{ctx, marks}, "", time.Now())
|
||||
out := map[string]any{"result": a, "node": "n0"}
|
||||
if err != nil {
|
||||
out = map[string]any{"error": err.Error()}
|
||||
|
||||
@@ -472,10 +472,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}
|
||||
}
|
||||
// Only the toolchain reports of nodes the catalogue places the build module on count (issue 1940, ADR 0549 §3): the
|
||||
// placement is settled first, so the toolchains settle through the gate already knowing it.
|
||||
// assignments is settled first, so the toolchains settle through the gate already knowing it.
|
||||
gate := buildrun.NewToolchainGate(sortedModules(), ix.PutToolchain, logger.Printf)
|
||||
// What each counted node's engine reads (ADR 0624 §1), for the mesh-wide hold of a manifest every node reads (ADR 0632
|
||||
// §2): asked through the node-engine seat's verb as the indexer's own account, for every node the placement names,
|
||||
// §2): asked through the node-engine seat's verb as the indexer's own account, for every node the assignments names,
|
||||
// kept in the state engine-reads once it is bound; each change weighs the holds again.
|
||||
reads := newEngineReads(calls.CallOn, gate, func() { ix.KickHolds(ctx) }, logger.Printf)
|
||||
gate.OnPlaced = reads.placed
|
||||
@@ -506,14 +506,14 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
process.AwaitBucket(ctx, js, conn, index.EngineReadsBucket, "what each node's engine reads is kept in memory only", logger.Printf).Ready(func(kv jetstream.KeyValue) {
|
||||
reads.bind(ctx, kv, errs, logger.Printf)
|
||||
})
|
||||
// The placement is the assigner's: read through its first view and its views family, never its bucket (DLV-F1r).
|
||||
placementErrs := make(chan error)
|
||||
// The assignments is the assigner's: read through its first view and its views family, never its bucket (DLV-F1r).
|
||||
assignmentsErrs := make(chan error)
|
||||
// The indexer's one pull of its durable on the shared events stream (process.EventHub): every handler is registered
|
||||
// before it starts.
|
||||
hub := process.NewEventHub(logger.Printf)
|
||||
followPlacement(ctx, hub, cred, calls.Call, gate, func(ctx context.Context) {
|
||||
followAssignments(ctx, hub, cred, calls.Call, gate, func(ctx context.Context) {
|
||||
if err := ix.Recompute(ctx); err != nil {
|
||||
logger.Printf("writing the modules again after the placement of a build module changed: %v", err)
|
||||
logger.Printf("writing the modules again after the assignments of a build module changed: %v", err)
|
||||
}
|
||||
}, findings, process.AwaitBucket(ctx, js, conn, firstViewsBucket, "indexer_first_views reads nothing and indexer_first_view_again is not heard", logger.Printf), logger.Printf)
|
||||
tools := newToolchainWatch(ctx, js, toolchainSink{
|
||||
@@ -649,7 +649,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
if err := hub.Handle(numberedSubject, versions); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = notHeard.Bind(process.EventsStream, "no package's series moves, no node's manifest-reads-changed and no placement view is heard until the indexer starts again", func() error {
|
||||
_ = notHeard.Bind(process.EventsStream, "no package's series moves, no node's manifest-reads-changed and no assignments view is heard until the indexer starts again", func() error {
|
||||
if err := hub.Start(ctx, js, cred); err != nil {
|
||||
stopped = cmp.Or(stopped, err)
|
||||
return err
|
||||
@@ -716,7 +716,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
return err
|
||||
case err := <-sourceErrs:
|
||||
return err
|
||||
case err := <-placementErrs:
|
||||
case err := <-assignmentsErrs:
|
||||
return err
|
||||
case err := <-tools.errs:
|
||||
return err
|
||||
|
||||
@@ -9,23 +9,23 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
|
||||
"mesh-delivery-indexer/internal/index"
|
||||
)
|
||||
|
||||
// engineReads is what each counted node's engine states it reads, for the indexer's mesh-wide hold (index.Holds; novox/hq
|
||||
// ADR 0632 §2, ADR 0624 §1), in the shape the builder keeps it: asked through the node-engine seat's verb manifest-reads
|
||||
// for every node the placement names, on the indexer's start, on the seat's event manifest-reads-changed for that node and
|
||||
// for every node the assignments names, on the indexer's start, on the seat's event manifest-reads-changed for that node and
|
||||
// when a person asks again (indexer_engine_reads, indexer_holds); kept in the indexer's state engine-reads, one key per
|
||||
// node, so a node that is away keeps its last statement. A node the placement no longer names is forgotten. Until the
|
||||
// placement is read no node is counted, and every hold stays.
|
||||
// node, so a node that is away keeps its last statement. A node the assignments no longer names is forgotten. Until the
|
||||
// assignments is read no node is counted, and every hold stays.
|
||||
type engineReads struct {
|
||||
keeper *enginereads.Keeper
|
||||
gate *buildrun.ToolchainGate
|
||||
mu sync.Mutex
|
||||
known bool // the placement is read
|
||||
known bool // the assignments is read
|
||||
nodes []string
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (r *engineReads) bind(ctx context.Context, kv jetstream.KeyValue, errs chan
|
||||
r.ask(nodes, "the state engine-reads is bound")
|
||||
}
|
||||
|
||||
// placed takes the placement in, once it is read and after each change of it: a node it names newly is asked, one it no
|
||||
// placed takes the assignments in, once it is read and after each change of it: a node it names newly is asked, one it no
|
||||
// longer names is forgotten, key and all.
|
||||
func (r *engineReads) placed() {
|
||||
nodes := r.gate.Nodes()
|
||||
@@ -64,7 +64,7 @@ func (r *engineReads) placed() {
|
||||
r.mu.Unlock()
|
||||
for _, node := range nodes {
|
||||
if first || !slices.Contains(before, node) {
|
||||
r.keeper.AskNode(node, "counted: the placement names it")
|
||||
r.keeper.AskNode(node, "counted: the assignments names it")
|
||||
}
|
||||
}
|
||||
for _, node := range before {
|
||||
@@ -94,7 +94,7 @@ func (r *engineReads) heard(m jetstream.Msg, logf func(string, ...any)) bool {
|
||||
}
|
||||
|
||||
// counted is index.Indexer.Counted: what each counted node states (nil, the baseline, for one with no statement), and
|
||||
// whether the placement is read.
|
||||
// whether the assignments is read.
|
||||
func (r *engineReads) counted() (map[string][]string, bool) {
|
||||
r.mu.Lock()
|
||||
nodes, known := slices.Clone(r.nodes), r.known
|
||||
|
||||
@@ -13,15 +13,15 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
sdkfindings "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/findings"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/heard"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
"mesh-delivery-indexer/internal/index"
|
||||
)
|
||||
|
||||
// findingsBucket is the indexer's own findings, which the condition keeper raises and clears (ADR 0564 §2): a value too
|
||||
// large for its views event, and its first view of the assigner's placement unanswered past its bound.
|
||||
// large for its views event, and its first view of the assigner's assignments unanswered past its bound.
|
||||
var findingsBucket = mcc.FindingsBucket(index.Module)
|
||||
|
||||
// readyOf binds a state's bucket: now for one bound at the start, once it appears for one awaited.
|
||||
@@ -109,15 +109,15 @@ const graphState = graph.State
|
||||
// where indexer_first_view_again asks.
|
||||
var firstViewsBucket = index.Module + "_" + views.StatusState
|
||||
|
||||
// placementTool is the assigner's first-view tool of its placement, as the indexer's invokes names it (ADR 0621 §2).
|
||||
const placementTool = index.AssignerModule + "." + index.PlacementState
|
||||
// assignmentsTool is the assigner's first-view tool of its assignments, as the indexer's invokes names it (ADR 0621 §2).
|
||||
const assignmentsTool = index.AssignerModule + "." + index.AssignmentsState
|
||||
|
||||
// followPlacement reads the seat mesh-placement's placement through the assigner's first view and then its views
|
||||
// followAssignments reads the seat mesh-module-assigner's assignments through the assigner's first view and then its views
|
||||
// family, never its bucket (ADR 0615 §1, DLV-F1r), into gate: each key as it changes, settled once answered, with
|
||||
// changed told whenever what the gate passes on changed. It waits for the first answer at most views.Bound, so a
|
||||
// normal start, the two owners coming up together, computes the fingerprints once; past it the indexer goes on, the gate
|
||||
// passing every report, and raises its condition naming the assigner and placement until the answer clears it.
|
||||
func followPlacement(ctx context.Context, hub *process.EventHub, cred process.Account, call func(string, any) (json.RawMessage, error),
|
||||
// passing every report, and raises its condition naming the assigner and assignments until the answer clears it.
|
||||
func followAssignments(ctx context.Context, hub *process.EventHub, cred process.Account, call func(string, any) (json.RawMessage, error),
|
||||
gate *buildrun.ToolchainGate, changed func(context.Context), findings, status *process.AwaitedBucket, logf func(string, ...any)) *views.Follower {
|
||||
finding := func(s views.Status, raised bool) {
|
||||
w := sdkfindings.Writer(ctx, findings.KV)
|
||||
@@ -133,11 +133,11 @@ func followPlacement(ctx context.Context, hub *process.EventHub, cred process.Ac
|
||||
}
|
||||
}
|
||||
var settled atomic.Bool // the first view answered: the gate's nodes are the counted ones from now on
|
||||
f := &views.Follower{Owner: index.AssignerModule, Tool: placementTool, State: index.PlacementState,
|
||||
Ask: views.AskVia(call, placementTool), Logf: logf,
|
||||
f := &views.Follower{Owner: index.AssignerModule, Tool: assignmentsTool, State: index.AssignmentsState,
|
||||
Ask: views.AskVia(call, assignmentsTool), Logf: logf,
|
||||
Raise: func(s views.Status) { finding(s, true) }, Clear: func(s views.Status) { finding(s, false) },
|
||||
Changed: func(key string, value json.RawMessage, deleted bool, at time.Time) {
|
||||
if gate.Placement(key, value, deleted, at) {
|
||||
if gate.Assignments(key, value, deleted, at) {
|
||||
changed(ctx)
|
||||
}
|
||||
if settled.Load() && gate.OnPlaced != nil {
|
||||
@@ -160,21 +160,21 @@ func followPlacement(ctx context.Context, hub *process.EventHub, cred process.Ac
|
||||
}
|
||||
settled.Store(true)
|
||||
if gate.OnPlaced != nil {
|
||||
gate.OnPlaced() // the placement is read: its nodes are the counted ones
|
||||
gate.OnPlaced() // the assignments is read: its nodes are the counted ones
|
||||
}
|
||||
close(answered)
|
||||
}()
|
||||
// The assigner's views family, on the module's one hub (the host starts it once every handler is registered); a family the
|
||||
// hub cannot register is the finding family-not-heard until the indexer starts again (ADR 0615 §5).
|
||||
_ = notHeardWriter(ctx, findings, logf).Bind(process.EventsStream, "the placement is the first view's alone, asked again by hand, until the indexer starts again", func() error {
|
||||
return hub.Handle("mesh.mod."+index.AssignerModule+".event."+views.EventPrefix+"*", views.Hear(ctx, map[string]*views.Follower{index.PlacementState: f}, logf))
|
||||
_ = notHeardWriter(ctx, findings, logf).Bind(process.EventsStream, "the assignments is the first view's alone, asked again by hand, until the indexer starts again", func() error {
|
||||
return hub.Handle("mesh.mod."+index.AssignerModule+".event."+views.EventPrefix+"*", views.Hear(ctx, map[string]*views.Follower{index.AssignmentsState: f}, logf))
|
||||
})
|
||||
wait, cancel := context.WithTimeout(ctx, views.Bound) // a context's bound, no timer (ADR 0437 §8)
|
||||
defer cancel()
|
||||
select {
|
||||
case <-answered:
|
||||
case <-wait.Done():
|
||||
logf("the assigner has not answered placement within %s: the indexer goes on, every node's toolchain report counting until it answers", views.Bound)
|
||||
logf("the assigner has not answered assignments within %s: the indexer goes on, every node's toolchain report counting until it answers", views.Bound)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ go 1.26.0
|
||||
require (
|
||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
|
||||
@@ -129,7 +129,7 @@ type Indexer struct {
|
||||
// capability read mesh-wide, waiting for every counted node-engine to state it. Nil, or Counted nil, holds nothing.
|
||||
Holds Store
|
||||
// Counted is what each counted node's engine states it reads (ADR 0624 §1; nil, the baseline, for a node with no
|
||||
// statement), and whether the counted nodes are known (the placement is read).
|
||||
// statement), and whether the counted nodes are known (the assignments is read).
|
||||
Counted func() (map[string][]string, bool)
|
||||
// AskEngines asks these nodes' engines what they read again (the node-engine seat's verb manifest-reads); nil asks none.
|
||||
AskEngines func(nodes []string, why string)
|
||||
|
||||
@@ -28,10 +28,10 @@ import (
|
||||
// it is written as its merge would have written it, judged and said. The assigner's hold of ADR 0614 §4 holds a want on
|
||||
// one node; this one holds the entry itself, which every node reads.
|
||||
//
|
||||
// "Counted" is ADR 0624 §1's set: every node the placement names, present or away, each read as its engine last stated
|
||||
// "Counted" is ADR 0624 §1's set: every node the assignments names, present or away, each read as its engine last stated
|
||||
// through the node-engine seat's verb manifest-reads (a node with no statement read as the baseline; the indexer's state
|
||||
// engine-reads, nodeengineclient/enginereads). Until the placement is read no node is counted and every hold stays. The hold moves
|
||||
// on the event manifest-reads-changed, the placement's change, a person's ask again, and a merge; never on a timer.
|
||||
// engine-reads, nodeengineclient/enginereads). Until the assignments is read no node is counted and every hold stays. The hold moves
|
||||
// on the event manifest-reads-changed, the assignments's change, a person's ask again, and a merge; never on a timer.
|
||||
const (
|
||||
// HoldsState is the indexer's state of held entries: the bucket mesh-delivery-indexer_holds, keyed as the modules state.
|
||||
HoldsState = "holds"
|
||||
@@ -126,7 +126,7 @@ func meshWide(needs []string) []string {
|
||||
}
|
||||
|
||||
// noneCounted is why a hold stays while no node is counted.
|
||||
const noneCounted = "no node is counted yet (the placement is not read), so the hold stays until it is"
|
||||
const noneCounted = "no node is counted yet (the assignments is not read), so the hold stays until it is"
|
||||
|
||||
// lacking is, of the counted nodes, each whose engine lacks some of needs, with what it lacks; or why no node is counted.
|
||||
func (ix *Indexer) lacking(needs []string) (map[string][]string, string) {
|
||||
@@ -232,7 +232,7 @@ func (ix *Indexer) dropHold(ctx context.Context, key, why string) error {
|
||||
}
|
||||
|
||||
// KickHolds moves every hold whose needs every counted node-engine now states, in the background, once at a time: on a
|
||||
// node's statement changed, the placement's change and the holds state's binding.
|
||||
// node's statement changed, the assignments's change and the holds state's binding.
|
||||
func (ix *Indexer) KickHolds(ctx context.Context) {
|
||||
if ix.Holds == nil || ix.Counted == nil {
|
||||
return
|
||||
|
||||
@@ -37,9 +37,9 @@ const (
|
||||
CheckerModule = "mesh-delivery-checker"
|
||||
BuildGoModule = "mesh-build-go"
|
||||
BuildNodeModule = "mesh-build-node"
|
||||
PlacementState = "placement"
|
||||
PlacementBucket = AssignerModule + "_" + PlacementState
|
||||
PlacementHistory = 5
|
||||
AssignmentsState = "assignments"
|
||||
AssignmentsBucket = AssignerModule + "_" + AssignmentsState
|
||||
AssignmentsHistory = 5
|
||||
SourceBucket = GateModule + "_source"
|
||||
PreviewsBucket = CheckerModule + "_previews"
|
||||
PreviewsHistory = 5
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
var OldSections = []string{"state", "logs", "streams", "objects"}
|
||||
|
||||
// Foundation are the modules that keep using the old sections for good: exactly the nine parts the bus module's own
|
||||
// composition, placement, build or delivery needs before it can answer (novox/hq ADR 0619 §1, §3): the bus module, the
|
||||
// composition, assignments, build or delivery needs before it can answer (novox/hq ADR 0619 §1, §3): the bus module, the
|
||||
// grants part, the indexer, the assigner, the tool runner, the builder, the checker, mesh-config and the vault. Their new
|
||||
// state goes on their old sections too, so a foundation manifest asking bus state in the new form is refused.
|
||||
var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools",
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
if m.Module != "mesh-delivery-indexer" {
|
||||
t.Errorf("module = %q", m.Module)
|
||||
}
|
||||
// 1126: placement is the seat mesh-placement's (ADR 0567): the indexer holds no placement state. ADR 0578 §1: it says
|
||||
// 1126: assignments is the seat mesh-module-assigner's (ADR 0567): the indexer holds no assignments state. ADR 0578 §1: it says
|
||||
// a refused manifest as manifest-refused, and (issue 1966 D3, 1966/AC10) a newly accepted one asking for bus state as
|
||||
// manifest-accepted, both events of the seat mesh-catalog it holds (ADR 0636 §4), so not among its `emits`.
|
||||
if own := slices.DeleteFunc(slices.Clone(m.Emits), func(e string) bool { return strings.HasPrefix(e, views.EventPrefix) }); !reflect.DeepEqual(own, []string{takein.TakeInPendingEvent, takein.TakeInTakenEvent}) {
|
||||
@@ -136,14 +136,14 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
t.Errorf("the indexer reads the bucket %s, its manifest's reads are %v (want %s)", bucket, m.Reads, read)
|
||||
}
|
||||
}
|
||||
// DLV-F1r (ADR 0615 §1): the assigner's placement is read through its first view and views family, never its state.
|
||||
// DLV-F1r (ADR 0615 §1): the assigner's assignments is read through its first view and views family, never its state.
|
||||
if want := []string{"mesh-build-go.toolchains", "mesh-build-node.toolchains", "mesh-delivery-builder.build-log", "mesh-delivery-checker.previews", "mesh-delivery-gate.source"}; !reflect.DeepEqual(sorted(m.Reads), want) {
|
||||
t.Errorf("reads = %v, want %v", m.Reads, want)
|
||||
}
|
||||
// "Reads FORGE" is a consumes of the indexer's stream: the broker grants only buckets by reads (mesh-control
|
||||
// internal/broker/state.go:62-93).
|
||||
// The registry seats' published and the builder's numbered fill each package's series (DLV-I5, design 51 *The versions*).
|
||||
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-delivery-assigner.views.placement",
|
||||
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-delivery-assigner.views.assignments",
|
||||
"mesh-delivery-builder.version.numbered", "mesh-delivery-checker.preview.written", "mesh-delivery-checker.try.ended", "mesh-delivery-checker.try.started", "mesh-delivery-checker.verdict", "mesh-delivery-forge-bridge.forge.**",
|
||||
"node-engine.manifest-reads-changed", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) { // ADR 0632 §2: the counted engines' reads
|
||||
t.Errorf("consumes = %v, want %v", m.Consumes, want)
|
||||
@@ -157,7 +157,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
// Three invokes: the file of a registered module's manifest and lock files (registered.go), the forge's trees (the named paths' objects, since the indexer is the one reader of a manifest's paths:
|
||||
// ADR 0499 clauses 2 and 3, ADR 0495 clause 1; and the module directories of the trunk, issue 1900) and whether a
|
||||
// commit is in another (issue 1900).
|
||||
if want := []string{"mesh-delivery-assigner.placement", "gitea.gitea_commit_trees", "gitea.gitea_contains", "gitea.gitea_get_file", "gitea.gitea_open_pulls", "mesh-delivery-checker.checker_tries", "mesh-delivery-forge-bridge.bridge_merged_message", "seat:node-engine.composition", "seat:node-engine.manifest-reads", "seat:mesh-hand-acts.hand-act"}; !reflect.DeepEqual(m.Invokes, want) {
|
||||
if want := []string{"mesh-delivery-assigner.assignments", "gitea.gitea_commit_trees", "gitea.gitea_contains", "gitea.gitea_get_file", "gitea.gitea_open_pulls", "mesh-delivery-checker.checker_tries", "mesh-delivery-forge-bridge.bridge_merged_message", "seat:node-engine.composition", "seat:node-engine.manifest-reads", "seat:mesh-hand-acts.hand-act"}; !reflect.DeepEqual(m.Invokes, want) {
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
// 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"go-package-registry.published",
|
||||
"mesh-build-go.toolchain.reported",
|
||||
"mesh-build-node.toolchain.reported",
|
||||
"mesh-delivery-assigner.views.placement",
|
||||
"mesh-delivery-assigner.views.assignments",
|
||||
"mesh-delivery-builder.version.numbered",
|
||||
"mesh-delivery-checker.preview.written",
|
||||
"mesh-delivery-checker.try.ended",
|
||||
@@ -119,7 +119,7 @@
|
||||
"mesh-delivery-gate.source"
|
||||
],
|
||||
"invokes": [
|
||||
"mesh-delivery-assigner.placement",
|
||||
"mesh-delivery-assigner.assignments",
|
||||
"gitea.gitea_commit_trees",
|
||||
"gitea.gitea_contains",
|
||||
"gitea.gitea_get_file",
|
||||
|
||||
Reference in New Issue
Block a user