diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judgeseats.go similarity index 52% rename from modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist.go rename to modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judgeseats.go index 697e256a..2bc14572 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judgeseats.go @@ -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. diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go index 9cdbc8ad..d819103b 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go @@ -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() diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist_test.go deleted file mode 100644 index b4d23411..00000000 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist_test.go +++ /dev/null @@ -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 -} diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist_wiring_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist_wiring_test.go new file mode 100644 index 00000000..f8e31bca --- /dev/null +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/seatlist_wiring_test.go @@ -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) + } + } +}