Send over mesh-sdk's composition: push, the pushes of plans and rollouts, held back, plan and the merge gate's passes (issue 1386)
Every send composes through composeSend (compose over the Input, perform what it named foreseen, read the stores again, compose again, refuse a declaration that still stands in for a value): push.go's two composing sites, composeForPush, declarationFor (the plan verb, which allocates on purpose for one named machine) and the merge gate's two passes and its trial assignment (composeSendValidated, which answers the node-engine's validator beside it). Each machine of a send reads the mesh afresh (freshInput), because the send before it made and recorded things. None calls declarationWith with Allocating any more. The private network is still computed up front, so a send refuses on it before anything is composed. Also here, as they share the helpers: whoResolvesOn, restOfTheMesh and restOfTheMeshOf in compose_sdk.go, and the planFor callers in held_back.go, plan.go and push.go.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
// The controller's composition on mesh-sdk's (novox/hq ADR 0453 clause 3, design 47 R4b, issues 1386 to 1388): every
|
||||
@@ -34,6 +35,12 @@ func withInputCache(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, inputCacheKey{}, &inputCache{by: map[string]*meshInput{}})
|
||||
}
|
||||
|
||||
// freshInput is ctx for one composition whose Input is read afresh and kept only until the composition makes something: a
|
||||
// send that is composed after another one made and recorded things (a push's machines, one after the other).
|
||||
func freshInput(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, inputCacheKey{}, &inputCache{by: map[string]*meshInput{}})
|
||||
}
|
||||
|
||||
// keptSignature is what the Input read depends on in ctx: which recorded modules each machine keeps at the build it runs
|
||||
// (recorded_kept.go), so an Input read for a person's push is never taken for one read for a gated send.
|
||||
func keptSignature(ctx context.Context) string {
|
||||
@@ -105,6 +112,43 @@ func generatorsOf(ctx context.Context, open *stores) (map[string]catalogue.Gener
|
||||
return compose.Generators(in.Input)
|
||||
}
|
||||
|
||||
// whoResolvesOn is the machines whose resolution answers a requirement, and why the others did not (whoResolves over the
|
||||
// Input): by what a module provides, not by its name. A machine whose set does not compose is in refused, said; one the mesh
|
||||
// could not be asked about is an error, never a refusal (novox/hq 04-ISSUES/152).
|
||||
func whoResolvesOn(ctx context.Context, open *stores, requirement string) (map[string]bool, map[string]string, error) {
|
||||
in, err := inputFor(ctx, open)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
on := map[string]bool{}
|
||||
refused := map[string]string{}
|
||||
for _, n := range in.sortedNodes() {
|
||||
resolved, err := compose.Resolve(in.Input, n.Name)
|
||||
switch {
|
||||
case compose.IsUnresolvable(err):
|
||||
refused[n.Name] = err.Error()
|
||||
continue
|
||||
case err != nil:
|
||||
return nil, nil, fmt.Errorf("whether %s answers %q cannot be read: %w", n.Name, requirement, err)
|
||||
}
|
||||
for _, m := range resolved.Plan.Modules {
|
||||
for _, offered := range m.Offers() {
|
||||
if offered == requirement {
|
||||
on[n.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return on, refused, nil
|
||||
}
|
||||
|
||||
// sortedNodes is the Input's nodes by name, the order the controller reads them in.
|
||||
func (in *meshInput) sortedNodes() []compose.Node {
|
||||
out := append([]compose.Node(nil), in.Nodes...)
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
return out
|
||||
}
|
||||
|
||||
// composeQuestion composes a node's declaration as a question asks it: nothing is made, and what a send would make is a
|
||||
// stand-in (Foreseeing) or left out (Reading). It also answers what the node-engine's validator refuses of it. The lines the
|
||||
// composition found are said where the controller says its own (standard error, or the writer an act gave).
|
||||
@@ -140,19 +184,26 @@ func markUnresolvable(err error) error {
|
||||
// then what it named foreseen is made (perform), the stores are read again and it is composed again. A declaration that
|
||||
// still names anything foreseen is refused, said, and never sent.
|
||||
func composeSend(ctx context.Context, open *stores, node string) (sendable, error) {
|
||||
declared, _, err := composeSendValidated(ctx, open, node)
|
||||
return declared, err
|
||||
}
|
||||
|
||||
// composeSendValidated is composeSend and what the node-engine's validator refuses of the declaration it composed, for the
|
||||
// merge gate's two passes (composedAndValidated in its Allocating mode).
|
||||
func composeSendValidated(ctx context.Context, open *stores, node string) (sendable, []string, error) {
|
||||
for attempt := 0; ; attempt++ {
|
||||
in, err := inputFor(ctx, open)
|
||||
if err != nil {
|
||||
return sendable{}, err
|
||||
return sendable{}, nil, err
|
||||
}
|
||||
d, _, err := compose.ComposeIn(in.Input, node, compose.Foreseeing)
|
||||
d, problems, err := compose.ComposeIn(in.Input, node, compose.Foreseeing)
|
||||
if err != nil {
|
||||
return sendable{}, markUnresolvable(err)
|
||||
return sendable{}, nil, markUnresolvable(err)
|
||||
}
|
||||
left := stillForeseen(d)
|
||||
resolved, rerr := compose.Resolve(in.Input, node)
|
||||
if rerr != nil {
|
||||
return sendable{}, markUnresolvable(rerr)
|
||||
return sendable{}, nil, markUnresolvable(rerr)
|
||||
}
|
||||
if len(resolved.Foreseen) > 0 {
|
||||
left = append(left, resolved.Foreseen...)
|
||||
@@ -167,23 +218,67 @@ func composeSend(ctx context.Context, open *stores, node string) (sendable, erro
|
||||
out.Bindings = boundToData(resolved.Plan, d.LeftOutWhy)
|
||||
if len(d.Filled) > 0 {
|
||||
if out.plans, err = handedPlans(in, node, resolved); err != nil {
|
||||
return sendable{}, err
|
||||
return sendable{}, nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
return out, problems, nil
|
||||
}
|
||||
if attempt > 0 {
|
||||
return sendable{}, fmt.Errorf("%s was composed twice and still names what only a send makes (%s): nothing is sent "+
|
||||
return sendable{}, nil, fmt.Errorf("%s was composed twice and still names what only a send makes (%s): nothing is sent "+
|
||||
"that stands in for a value", node, strings.Join(left, "; "))
|
||||
}
|
||||
if _, err := perform(ctx, open, in, node, d); err != nil {
|
||||
return sendable{}, err
|
||||
return sendable{}, nil, err
|
||||
}
|
||||
// What was made is in the stores now: the next read is a fresh one.
|
||||
forgetInput(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// restOfTheMesh is what every other machine holds and offers (theRestOfTheMesh over the Input), for a question: the lines it
|
||||
// finds are said where the controller says its own, and a seat it would pass on is passed by the next send (perform), not by
|
||||
// the question. shelf, when given, replaces the Input's catalogue: a caller composing a change's shelf.
|
||||
func restOfTheMesh(ctx context.Context, open *stores, shelf map[string]catalogue.Manifest, exclude string) (catalogue.World, error) {
|
||||
in, err := inputFor(ctx, open)
|
||||
if err != nil {
|
||||
return catalogue.World{}, err
|
||||
}
|
||||
input := in.Input
|
||||
if shelf != nil {
|
||||
input.Catalogue = shelf
|
||||
}
|
||||
world, passes, left, err := compose.TheRestOfTheMesh(input, exclude)
|
||||
if err != nil {
|
||||
return catalogue.World{}, err
|
||||
}
|
||||
var w io.Writer = os.Stderr
|
||||
if lw, ok := ctx.Value(leftOutKey{}).(io.Writer); ok {
|
||||
w = lw
|
||||
}
|
||||
for _, l := range left {
|
||||
fmt.Fprintln(w, l.String())
|
||||
}
|
||||
for _, line := range passes.Said {
|
||||
fmt.Fprintln(w, line)
|
||||
}
|
||||
return world, nil
|
||||
}
|
||||
|
||||
// restOfTheMeshOf is restOfTheMesh for a caller that holds the inventory alone: the other stores the Input reads are opened for
|
||||
// the call and let go of again.
|
||||
func restOfTheMeshOf(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest, exclude string) (catalogue.World, error) {
|
||||
open := &stores{inventory: inv}
|
||||
defer func() {
|
||||
if open.licences != nil {
|
||||
open.licences.Close()
|
||||
}
|
||||
if open.identity != nil {
|
||||
open.identity.Close()
|
||||
}
|
||||
}()
|
||||
return restOfTheMesh(ctx, open, shelf, exclude)
|
||||
}
|
||||
|
||||
// sayFound says the lines a composition found while composing, once each: a machine left out of the rest of the mesh, a seat
|
||||
// that two claimants could take, the bus's users left out for want of a credential.
|
||||
func sayFound(ctx context.Context, d compose.Declared) {
|
||||
|
||||
Reference in New Issue
Block a user