AS10: the assignment graph finds a cycle among the waits within one step and says it
Builds: design 47 G §272, §356 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -34,6 +34,8 @@ type graphKeeper struct {
|
||||
source int64
|
||||
ok bool
|
||||
said string
|
||||
// cycled is the cycles last said (assigngraph.Cycles).
|
||||
cycled string
|
||||
// pins removes a revision that ceased to be current once nothing pins it (pins.go); nil keeps every revision.
|
||||
pins *pinKeeper
|
||||
// computed is the revision computed by the last pass, with its entries: the edges the wants wait on (actingwaits.go).
|
||||
@@ -60,6 +62,12 @@ func (p *passer) keepGraph(ctx context.Context) {
|
||||
p.logf("the assignment graph leaves out %s", u)
|
||||
}
|
||||
}
|
||||
if cycles := strings.Join(c.Cycles, "; "); cycles != g.cycled {
|
||||
g.cycled = cycles
|
||||
if cycles != "" {
|
||||
p.logf("the assignment graph holds a cycle among the waits within one step, and its entries wait for each other: %s", cycles)
|
||||
}
|
||||
}
|
||||
if g.has && g.ok && assigngraph.Same(g.last, c.Revision) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ type Computed struct {
|
||||
// Uncomputed is each placed module whose entry could not be computed, as "<node>/<module>: why", sorted: it is said,
|
||||
// never left out in silence.
|
||||
Uncomputed []string
|
||||
// Cycles are the cycles among the waits within one step (Cycles, design 47 G §272): said, never left in silence.
|
||||
Cycles []string
|
||||
}
|
||||
|
||||
// placedAt is one placed module with what its entry is computed from.
|
||||
@@ -212,6 +214,7 @@ func Compute(in Inputs) Computed {
|
||||
rev.Entries = append(rev.Entries, meshwants.NodeEntryRef{Node: p.node, Module: p.placed.Module, Digest: d})
|
||||
}
|
||||
out.Revision = rev
|
||||
out.Cycles = Cycles(slices.Collect(maps.Values(out.Entries)))
|
||||
slices.Sort(out.Uncomputed)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package assigngraph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
)
|
||||
|
||||
// Cycles are the cycles among the waits within one step (design 47 G §272, ADR 0656): an entry waits for its acting parts in the step an
|
||||
// edge names, so two acting parts acting on each other in one step wait for each other for ever. Each is `<step>: a -> b -> a`, by
|
||||
// node and module, sorted. Edges of two steps never close a cycle together: the indexer taking in the checker and the checker checking
|
||||
// the indexer are none, and a holder's own inputs are no edge here (the graph gives an acting edge only from a part to the entries it
|
||||
// acts on, never to what the part is built from).
|
||||
func Cycles(entries []meshwants.AssignmentEntry) []string {
|
||||
type key struct{ step, at string }
|
||||
next := map[key][]string{}
|
||||
steps := map[string]bool{}
|
||||
for _, e := range entries {
|
||||
for _, edge := range e.Edges {
|
||||
if edge.Kind != EdgeActing {
|
||||
continue
|
||||
}
|
||||
k := key{edge.Step, e.Node + "/" + e.Module}
|
||||
next[k] = append(next[k], edge.Node+"/"+edge.Module)
|
||||
steps[edge.Step] = true
|
||||
}
|
||||
}
|
||||
var out []string
|
||||
for _, step := range slices.Sorted(maps.Keys(steps)) {
|
||||
const (
|
||||
open = iota + 1
|
||||
done
|
||||
)
|
||||
state := map[string]int{}
|
||||
var path []string
|
||||
var walk func(at string)
|
||||
walk = func(at string) {
|
||||
state[at] = open
|
||||
path = append(path, at)
|
||||
for _, to := range next[key{step, at}] {
|
||||
switch state[to] {
|
||||
case 0:
|
||||
walk(to)
|
||||
case open:
|
||||
from := slices.Index(path, to)
|
||||
cycle := slices.Clone(path[from:])
|
||||
// start at the smallest member so one cycle is said once, whichever member is met first
|
||||
lowest := slices.Index(cycle, slices.Min(cycle))
|
||||
cycle = append(cycle[lowest:], cycle[:lowest]...)
|
||||
out = append(out, fmt.Sprintf("%s: %s -> %s", step, strings.Join(cycle, " -> "), cycle[0]))
|
||||
}
|
||||
}
|
||||
path = path[:len(path)-1]
|
||||
state[at] = done
|
||||
}
|
||||
starts := map[string]bool{}
|
||||
for k := range next {
|
||||
if k.step == step {
|
||||
starts[k.at] = true
|
||||
}
|
||||
}
|
||||
for _, at := range slices.Sorted(maps.Keys(starts)) {
|
||||
if state[at] == 0 {
|
||||
walk(at)
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.Sort(out)
|
||||
return slices.Compact(out)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package assigngraph
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
)
|
||||
|
||||
// Design 47 G §272 (AS10): a cycle among the waits within one step is found; an acting part acting on itself's other module, two parts
|
||||
// acting on each other in different steps, and a chain are none.
|
||||
func TestACycleAmongTheWaitsWithinOneStepIsFound(t *testing.T) {
|
||||
act := func(node, module, step string) meshwants.Edge {
|
||||
return meshwants.Edge{Kind: EdgeActing, Node: node, Module: module, Step: step}
|
||||
}
|
||||
entry := func(module string, edges ...meshwants.Edge) meshwants.AssignmentEntry {
|
||||
return meshwants.AssignmentEntry{Node: "ace", Module: module, Edges: edges}
|
||||
}
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
entries []meshwants.AssignmentEntry
|
||||
want []string
|
||||
}{
|
||||
{"two parts on each other in one step", []meshwants.AssignmentEntry{entry("a", act("ace", "b", "compose")), entry("b", act("ace", "a", "compose"))},
|
||||
[]string{"compose: ace/a -> ace/b -> ace/a"}},
|
||||
{"three in a ring, said once", []meshwants.AssignmentEntry{entry("c", act("ace", "a", "build")), entry("a", act("ace", "b", "build")), entry("b", act("ace", "c", "build"))},
|
||||
[]string{"build: ace/a -> ace/b -> ace/c -> ace/a"}},
|
||||
{"the indexer taking in the checker, the checker checking the indexer", []meshwants.AssignmentEntry{entry("checker", act("ace", "indexer", "take-in")), entry("indexer", act("ace", "checker", "check"))}, nil},
|
||||
{"a chain", []meshwants.AssignmentEntry{entry("a", act("ace", "b", "compose")), entry("b", act("ace", "c", "compose")), entry("c")}, nil},
|
||||
{"provisioning edges are no wait of a step", []meshwants.AssignmentEntry{entry("a", meshwants.Edge{Kind: EdgeProvisioning, Node: "ace", Module: "b"}), entry("b", meshwants.Edge{Kind: EdgeProvisioning, Node: "ace", Module: "a"})}, nil},
|
||||
} {
|
||||
if got := Cycles(c.entries); !slices.Equal(got, c.want) {
|
||||
t.Errorf("%s: cycles %v, want %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user