G §683: a start composes the modules whose kept want and entry it holds before its other inputs are read
The node's own record comes from the node-engine's statement of itself, taken in memory with no bus. Builds: design 47 G A-117, A-120, §683, A-95 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -125,10 +125,19 @@ func composeFromEntries(t *testing.T) {
|
||||
mu.Unlock()
|
||||
env2 := b.env()
|
||||
env2.Entry, env2.ComposedState = EntryCall(ask), state
|
||||
// The node's own record, stated by the node-engine of itself in memory (A-120).
|
||||
env2.NodeRecord = func() (nodeengine.NodeRecord, bool) {
|
||||
return nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}, true
|
||||
}
|
||||
c2 := New(env2)
|
||||
if _, err := c2.Watch(ctx, "n1.>"); err != nil {
|
||||
out2, err := c2.Watch(ctx, "n1.>")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// And composes the module it kept a want and an entry of before any input is read, nothing of the others (design 47 G §683, A-117).
|
||||
if u := next(t, out2); u.Key != lemursKey || u.Deleted {
|
||||
t.Errorf("the early pass handed %q, want the composition %s of lemurs", u.Key, lemursKey)
|
||||
}
|
||||
c2.mu.Lock()
|
||||
kept, keptWant := c2.keptEnt[fp], c2.wants["lemurs"].Fp
|
||||
c2.mu.Unlock()
|
||||
|
||||
@@ -61,6 +61,9 @@ const ResolvedBucket = "mesh-config_resolved"
|
||||
// Env is what the composer needs of the node.
|
||||
type Env struct {
|
||||
Node string
|
||||
// NodeRecord is the node's own record as the node-engine states it of itself, held in memory; the early pass of a start composes
|
||||
// with it before the have bucket is read (design 47 G A-120). Nil, or false, has none.
|
||||
NodeRecord func() (nodeengine.NodeRecord, bool)
|
||||
// EntryNotFound is told when the catalog half answered it holds no entry of a fingerprint (notFound), and when that is no longer so:
|
||||
// the entry answered, or nothing the node holds names it (design 47 G §614, A-117). Nil tells nobody.
|
||||
EntryNotFound func(fp string, notFound bool)
|
||||
@@ -241,6 +244,8 @@ type Composer struct {
|
||||
asked chan chan Pass
|
||||
// passing is set while a pass is made.
|
||||
passing atomic.Bool
|
||||
// early is true while the early pass of a start runs (design 47 G §683, A-117).
|
||||
early atomic.Bool
|
||||
// last is how the last pass went, under mu.
|
||||
last Pass
|
||||
// watching says the composer's loop runs (Watch), so an ask of a pass can be answered.
|
||||
@@ -611,6 +616,20 @@ func (c *Composer) Watch(ctx context.Context, _ string) (<-chan agent.Update, er
|
||||
// input moves (issue 1126, 2026-10-28).
|
||||
var again <-chan time.Time
|
||||
var replies []chan Pass
|
||||
// At a start, until its assignments, haves and resolved values are read, the node composes the modules whose kept want and
|
||||
// kept entry it holds, so the bus module and the indexer start while both are down (design 47 G §683, A-95, A-117).
|
||||
if c.standing() {
|
||||
c.passing.Store(true)
|
||||
updates := c.composeEarly(ctx)
|
||||
c.passing.Store(false)
|
||||
for _, u := range updates {
|
||||
select {
|
||||
case out <- u:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
// Drain whatever came, then compose once for all of it.
|
||||
select {
|
||||
@@ -975,6 +994,7 @@ func (c *Composer) takeValue(u agent.Update) {
|
||||
// compose composes the node once and answers the updates to hand: each composition whose digest differs from the one
|
||||
// handed, and a deletion for each handed one no want names any more.
|
||||
func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
early := c.early.Load()
|
||||
pass := Pass{}
|
||||
defer func() {
|
||||
pass.At = time.Now()
|
||||
@@ -1013,6 +1033,9 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
if module == agent.NodeModule {
|
||||
continue
|
||||
}
|
||||
if early && !c.stands(module, w) {
|
||||
continue
|
||||
}
|
||||
fps[module] = w.Fp
|
||||
if m, ok := c.modules[module]; ok && m.Fp2 != w.Fp {
|
||||
behind = append(behind, module)
|
||||
@@ -1113,6 +1136,11 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
} else {
|
||||
c.paid()
|
||||
}
|
||||
if early {
|
||||
// Inputs not yet read may refuse it: nothing is marked, and the full pass follows when they are read.
|
||||
c.env.Say("the node's early composition of the modules it kept is refused, and the full pass follows: " + why)
|
||||
return nil
|
||||
}
|
||||
pass.Refused = why
|
||||
// Said before the agent marks the modules: the marks read each have first, which a paused bus delays.
|
||||
c.sayOnce("the node cannot compose its modules, and keeps running what it runs: " + why)
|
||||
@@ -1232,6 +1260,9 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
}
|
||||
pubComps[key] = comp
|
||||
}
|
||||
if early {
|
||||
return out // what the early pass handed is composed from kept state alone: nothing is published from it
|
||||
}
|
||||
c.pubAt = &published{scope: scope, fps: maps.Clone(fps), comps: pubComps, held: maps.Clone(held), placed: placedMods,
|
||||
ran: ranOf(running)}
|
||||
c.pubDue = true
|
||||
@@ -2431,3 +2462,69 @@ func (c *Composer) EngineModule() string {
|
||||
defer c.mu.Unlock()
|
||||
return c.engine
|
||||
}
|
||||
|
||||
// stands says the want of a module stands for its placement in the early pass of a start: its want is a kept one the live read has
|
||||
// not answered, and the entry of its build is kept (design 47 G A-117).
|
||||
func (c *Composer) stands(module string, w meshwants.WantsByWantNodeModuleValue) bool {
|
||||
if !c.byEntries() || !c.keptWants[module] {
|
||||
return false
|
||||
}
|
||||
k, ok := c.keptEnt[w.Fp]
|
||||
return ok && k.entry.Module != ""
|
||||
}
|
||||
|
||||
// standing says the composer holds a kept want and the kept entry of its build, so an early pass has something to compose.
|
||||
func (c *Composer) standing() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for module, w := range c.wants {
|
||||
if module != agent.NodeModule && c.stands(module, w) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// composeEarly is the pass of a start before the assignments, haves and resolved values are read: it composes only the modules whose
|
||||
// kept want and kept entry it holds, a kept want standing for their placement, and leaves every other module as it runs; the full
|
||||
// pass follows once the inputs are read (design 47 G §683, A-95, A-117).
|
||||
func (c *Composer) composeEarly(ctx context.Context) []agent.Update {
|
||||
node := c.env.Node
|
||||
c.mu.Lock()
|
||||
_, hadRecord := c.records[node]
|
||||
if !hadRecord && c.env.NodeRecord != nil {
|
||||
if rec, ok := c.env.NodeRecord(); ok {
|
||||
c.records[node] = rec
|
||||
} else {
|
||||
hadRecord = true // nothing laid, nothing to take away
|
||||
}
|
||||
}
|
||||
_, had := c.assignments[node]
|
||||
if !had {
|
||||
p := meshmoduleassigner.Assignments{Node: node}
|
||||
for module, w := range c.wants {
|
||||
if module != agent.NodeModule && c.stands(module, w) {
|
||||
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: module})
|
||||
}
|
||||
}
|
||||
sort.Slice(p.Modules, func(i, j int) bool { return p.Modules[i].Module < p.Modules[j].Module })
|
||||
c.assignments[node] = p
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.early.Store(true)
|
||||
defer func() {
|
||||
c.early.Store(false)
|
||||
if !hadRecord {
|
||||
c.mu.Lock()
|
||||
delete(c.records, node)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
if !had {
|
||||
c.mu.Lock()
|
||||
delete(c.assignments, node)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
c.env.Say("the node composes the modules it kept a want and an entry of, before its other inputs are read")
|
||||
return c.compose(ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user