J25 A-293: a placed module's failed newest build said as refused in by-entries mode, on the builder's build-ended event
Reading the catalog half by fingerprint, the node-engine no longer reads the builder's builds, so #224's refused key was never written there and a reader waited on a build that will never be done. The engine now hears the builder's build.ended stream, read whole at start then followed (as the assigner hears it); the last trunk end of a placed module that ended failed is written as the refused key with the event's words, a later done end deletes it, an end naming a branch moves nothing. The manifest consumes mesh-delivery-builder.build.ended.**. Builds: design 47 G A-293 (with §421, §628), ADR 0593 §6, issue 1966 U1/U8 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -694,6 +694,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
composerNow.Store(composer) // what the verb compose asks a pass of, this run's alone
|
||||
session.SetManifestOf(composer.ManifestOf) // the event running names the manifest of the build that runs
|
||||
go followRunning(run, session, composer, say)
|
||||
go followBuildEnds(run, session, composer, say)
|
||||
apply.UseEngineModule(composer.EngineModule) // the guard of the engine's own trees fixes no name (§527)
|
||||
env.Composed, env.SelfComposed, env.Policy, env.ComposedToday = composer, true, composer.Policy, composer.ComposedToday
|
||||
env.Recovery = recoveryEnv(session, composer)
|
||||
@@ -1204,11 +1205,28 @@ func composerEnv(session *link.Session, node string, say func(string), hooks com
|
||||
// followRunning keeps the composer told what every node runs, from the seat node-engine's event running, for the run it is
|
||||
// in: the stream or the link not being there is said and tried again after a backoff, never polled.
|
||||
func followRunning(run context.Context, session *link.Session, composer *selfcompose.Composer, say func(string)) {
|
||||
keepFollowing(run, "this node does not hear what the nodes run, and tries again: ", func(ctx context.Context) error {
|
||||
return session.FollowRunning(ctx, composer.NoteRunning, composer.RunningRead)
|
||||
}, composer.RunningLost, say)
|
||||
}
|
||||
|
||||
// followBuildEnds keeps the composer told how the builder's builds ended, from its event build.ended, for the run it is in
|
||||
// (design 47 G A-293): a placed module's newest build that ended failed is said as a refusal in the composition state.
|
||||
func followBuildEnds(run context.Context, session *link.Session, composer *selfcompose.Composer, say func(string)) {
|
||||
keepFollowing(run, "this node does not hear how the builder's builds end, so a failed build is not said as refused, and it tries again: ",
|
||||
func(ctx context.Context) error {
|
||||
return session.FollowBuildEnds(ctx, composer.NoteBuildEnded, composer.BuildEndsRead)
|
||||
}, composer.BuildEndsLost, say)
|
||||
}
|
||||
|
||||
// keepFollowing follows a stream with follow for the run, lost called each time it ends; why it ended is said once until it
|
||||
// changes, and it is followed again after a backoff, never polled.
|
||||
func keepFollowing(run context.Context, line string, follow func(context.Context) error, lost func(), say func(string)) {
|
||||
said := ""
|
||||
for try := 1; run.Err() == nil; try++ {
|
||||
began := time.Now()
|
||||
err := session.FollowRunning(run, composer.NoteRunning, composer.RunningRead)
|
||||
composer.RunningLost()
|
||||
err := follow(run)
|
||||
lost()
|
||||
if run.Err() != nil {
|
||||
return
|
||||
}
|
||||
@@ -1216,7 +1234,7 @@ func followRunning(run context.Context, session *link.Session, composer *selfcom
|
||||
try = 1
|
||||
}
|
||||
if why := fmt.Sprint(err); why != said {
|
||||
say("this node does not hear what the nodes run, and tries again: " + why)
|
||||
say(line + why)
|
||||
said = why
|
||||
}
|
||||
select {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshrecovery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
"github.com/novox/mesh-host/internal/link"
|
||||
@@ -131,6 +132,41 @@ func TestNatsTheRecoveryStepAsksHearsBoundsAndCatchesUpOverTheBus(t *testing.T)
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("the bound's end, the bus's marker, was not heard")
|
||||
}
|
||||
|
||||
// Design 47 G A-293: over the same session, the builder's build ends the composer follows: the stream read whole first (the
|
||||
// last end of each module and fingerprint, in the stream's order), then the read's end, then each end said after it.
|
||||
if _, err := b.js.AddStream(&nats.StreamConfig{Name: link.BuildEndsStream, Subjects: []string{link.BuildEndsSubject}, MaxMsgsPerSubject: 1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ended := func(fp, outcome, why string) {
|
||||
raw, _ := json.Marshal(builds.BuildEnded{Module: "db", Fingerprint: fp, Outcome: outcome, Error: why})
|
||||
if _, err := b.js.Publish("mesh.stream.mesh-delivery-builder.event."+builds.BuildEndedEventName("db", fp), raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
ended("f1", builds.BuildFailed, "first")
|
||||
ended("f1", builds.BuildFailed, "no go")
|
||||
ended("f0", builds.BuildDone, "")
|
||||
heard := make(chan string, 8)
|
||||
follow, stopFollow := context.WithCancel(ctx)
|
||||
defer stopFollow()
|
||||
go func() {
|
||||
_ = session.FollowBuildEnds(follow, func(e builds.BuildEnded) { heard <- e.Fingerprint + ":" + e.Outcome + ":" + e.Error },
|
||||
func() { heard <- "read" })
|
||||
}()
|
||||
for i, want := range []string{"f1:failed:no go", "f0:done:", "read", "f2:failed:later"} {
|
||||
if want == "f2:failed:later" {
|
||||
ended("f2", builds.BuildFailed, "later")
|
||||
}
|
||||
select {
|
||||
case got := <-heard:
|
||||
if got != want {
|
||||
t.Fatalf("build end %d heard %q, want %q", i, got, want)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("build end %d, %q, was not heard", i, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// startSession is a session on the node's own connection, with no node agent run on it.
|
||||
|
||||
Reference in New Issue
Block a user