CK9: the checker follows the seat list through mesh-sdk's seatlist.Part, its own seatlist.go goes
Builds: design 47 G §204, §264, §320 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
+2
-27
@@ -7,28 +7,12 @@ import (
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
)
|
||||
|
||||
// The checker reads the seat list as every part does (novox/hq ADR 0635 §1, ADR 0636): through the seat mesh-catalog's verb
|
||||
// seat-definitions, its two events heard as handlers of the module's event hub, from the bootstrapped definitions until the seat
|
||||
// has answered. What the reader holds is given to catalogue at every change, which the module graph is worked out with, and to the
|
||||
// indexer's judge as a request's Seats, which the manifests of a change are judged against.
|
||||
|
||||
// followSeatList starts the checker's reader of the seat list over the bus; the hub is started by the caller once every handler is registered.
|
||||
func followSeatList(bus seatlist.Bus, logf func(string, ...any), changed func()) *seatlist.Reader {
|
||||
reader, err := seatlist.Start(seatlist.Config{Bus: bus, Bootstrap: seatlist.Bootstrapped(),
|
||||
OnChange: func(seats []catalogue.Seat) { catalogue.UseSeats(seats); changed() }})
|
||||
if err != nil {
|
||||
logf("the checker cannot follow its seat list, so no manifest is judged until it starts again: %v", err)
|
||||
}
|
||||
if reader != nil {
|
||||
catalogue.UseSeats(reader.Seats())
|
||||
}
|
||||
return reader
|
||||
}
|
||||
// The checker reads the seat list through mesh-sdk's seatlist.Part, the one wiring (design 47 G §204): what the reader holds is the
|
||||
// indexer's judge's Seats of a request, which the manifests of a change are judged against.
|
||||
|
||||
// seatList is the reader as the check's SeatList: the held definitions raw, and an error while the seat has not answered.
|
||||
type seatList struct{ reader *seatlist.Reader }
|
||||
@@ -52,15 +36,6 @@ func (s seatList) Definitions() ([]json.RawMessage, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// readIfUnread is called when the checker's hub is heard. The seat may have answered, and said that it does, before the
|
||||
// checker's consumer existed, whose events start at the stream's end: the first read found it silent and nothing else reads
|
||||
// again until its next event, so while the list is unread it is read now, on a goroutine of its own (ADR 0636 §1).
|
||||
func readIfUnread(reader *seatlist.Reader) {
|
||||
if reader != nil {
|
||||
reader.ReloadIfUnread()
|
||||
}
|
||||
}
|
||||
|
||||
// buildsWatch is what is done when the build modules may have changed (design 47 G A-82): the seat list names the build seats
|
||||
// and the indexer's modules the holders of the seats, so a change of either may name a build module not watched yet. It is set once
|
||||
// the parts that watch the build modules are made, and called from then on.
|
||||
@@ -161,7 +161,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
hub := process.NewEventHub(logger.Printf)
|
||||
reader := &views.ProcessReader{Module: check.CheckerModule, Call: caller.Call, Logf: logger.Printf, Hub: hub}
|
||||
var buildsChanged buildsWatch
|
||||
seats := seatList{reader: followSeatList(seatlist.ProcessBus(caller.Call, hub), logger.Printf, buildsChanged.Changed)}
|
||||
seatPart := seatlist.NewPart("checker", nil)
|
||||
seats := seatList{reader: seatPart.Follow(seatlist.ProcessBus(caller.Call, hub), buildsChanged.Changed, logger.Printf)}
|
||||
// The finding writer is made once the checker is (its observe reads the checker's kept checks); until then a first view
|
||||
// unanswered is said in the log alone.
|
||||
var findings atomic.Pointer[checkerFindings]
|
||||
@@ -503,7 +504,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
reader.Start(ctx, js, cred, process.AwaitBucket(ctx, js, conn, firstViewsBucket, "checker_first_views reads nothing and checker_first_view_again is not heard", logger.Printf))
|
||||
// Every part has registered. Where the consumer is not there yet the hub binds it as soon as it is, and the seat list is read
|
||||
// again then if it is still unread.
|
||||
if err := hub.StartUntilBound(ctx, js, cred, nil, func() { readIfUnread(seats.reader) }); err != nil {
|
||||
if err := hub.StartUntilBound(ctx, js, cred, nil, seatPart.EventsLive); err != nil {
|
||||
logger.Printf("%v, so no package registry's published is heard until the consumer is there; the checker tries again", err)
|
||||
}
|
||||
defer hub.Stop()
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
)
|
||||
|
||||
// silentThenAnswering is a seat that is silent at the reader's first read and answers after.
|
||||
type silentThenAnswering struct{ answering atomic.Bool }
|
||||
|
||||
func (b *silentThenAnswering) Ask(string, any) (json.RawMessage, error) {
|
||||
if !b.answering.Load() {
|
||||
return nil, errors.New("not connected yet")
|
||||
}
|
||||
return json.RawMessage(`{"revision":1,"seats":[]}`), nil
|
||||
}
|
||||
|
||||
func (b *silentThenAnswering) Subscribe(string, func(string, json.RawMessage) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// A seat that spoke before the checker's consumer existed is read when the hub is heard (ADR 0636 §1).
|
||||
func TestTheSeatListIsReadWhenTheHubIsHeard(t *testing.T) {
|
||||
bus := &silentThenAnswering{}
|
||||
reader := followSeatList(bus, t.Logf, func() {})
|
||||
<-reader.Ready()
|
||||
if reader.Status().Heard {
|
||||
t.Fatal("a silent seat was heard")
|
||||
}
|
||||
bus.answering.Store(true)
|
||||
readIfUnread(reader)
|
||||
for deadline := time.Now().Add(5 * time.Second); !reader.Status().Heard; time.Sleep(10 * time.Millisecond) {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("the list was not read when the hub was heard")
|
||||
}
|
||||
}
|
||||
var _ seatlist.Bus = bus
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The checker wires no reader of the seat list of its own (design 47 G §204, CK9): it follows the list through mesh-sdk's
|
||||
// seatlist.Part, and its code holds no seatlist.Start nor a file of its own for the list.
|
||||
func TestTheCheckerWiresNoSeatListReaderOfItsOwn(t *testing.T) {
|
||||
if _, err := os.Stat("seatlist.go"); err == nil {
|
||||
t.Error("seatlist.go is the checker's own wiring of the seat list: mesh-sdk's seatlist.Part is the one wiring")
|
||||
}
|
||||
files, _ := filepath.Glob("*.go")
|
||||
for _, f := range files {
|
||||
if strings.HasSuffix(f, "_test.go") {
|
||||
continue
|
||||
}
|
||||
src, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(src), "seatlist.Start(") {
|
||||
t.Errorf("%s starts a seat list reader of its own: use seatlist.Part.Follow", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user