V-2: W1.13 (views adopt): a reader adopts a follower a seat's client made, so its callers write no glue
Six readers of the catalog half each wrote the same glue (dev-G-links' finding): the follower's conditions and view-waits, the seat event's subscription, Keys/Get/Watch over its values and a wrap of the first-view tools. Reader.Adopt and ProcessReader.Adopt take the client's follower with its seat's event and handler: the reader writes its findings (unanswered, view-waits; Follow's followers now write view-waits too), reads and lists it as any first view, keeps its own Changed after the reader's, and hears the seat's event (stdio: <seat>.<event>; process: the durable's mesh.seat.<seat>.event.<event>, also on a shared hub) into the handler. A subtest of TestViews. Builds: design 47 G §262, §260 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
+104
-1
@@ -14,6 +14,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -55,6 +56,8 @@ type ProcessReader struct {
|
||||
mu sync.Mutex
|
||||
pics map[string]*Picture // by bucket name <owner>_<state>
|
||||
order []string
|
||||
// adopted are the handlers of the adopted followers' seat events (Adopt), by the event's subject.
|
||||
adopted map[string]func(context.Context, []byte) error
|
||||
}
|
||||
|
||||
func (r *ProcessReader) logf(format string, args ...any) {
|
||||
@@ -101,6 +104,85 @@ func (r *ProcessReader) Follow(owner, state, tool string) (*Picture, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Adopt takes a follower a seat's client made (its Ask a seat's verb, such as the catalog half's view) as one of the
|
||||
// process's first views, so the client's callers write no glue: the reader sets its conditions (the unanswered first
|
||||
// view and view-waits, in the reader's findings), its log and clock, and its picture (`<owner>_<state>`) hands each
|
||||
// change on before the follower's own Changed, set before. Start keeps its status, hears its hand asks, and asks it with
|
||||
// the others; its events are the seat's event (seatdef's EventSubject of seat and event, on the module's durable
|
||||
// consumer, as its `consumes` names it), handed to hear, the client's handler, and acknowledged. Call it before Start;
|
||||
// a state followed already is refused.
|
||||
func (r *ProcessReader) Adopt(f *Follower, seat, event string, hear func(context.Context, []byte) error) (*Picture, error) {
|
||||
if f == nil || f.Owner == "" || f.State == "" || seat == "" || event == "" || hear == nil {
|
||||
return nil, errors.New("views: an adopted follower names its owner and state, and its seat's event and handler")
|
||||
}
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.pics == nil {
|
||||
r.pics = map[string]*Picture{}
|
||||
}
|
||||
bucket := f.Owner + "_" + f.State
|
||||
if r.pics[bucket] != nil {
|
||||
return nil, fmt.Errorf("views: %s's %s is followed already", f.Owner, f.State)
|
||||
}
|
||||
p := &Picture{bucket: bucket, ctx: context.Background(), f: f}
|
||||
if f.Logf == nil {
|
||||
f.Logf = r.Logf
|
||||
}
|
||||
if f.Now == nil {
|
||||
f.Now = r.Now
|
||||
}
|
||||
if f.Bound == 0 {
|
||||
f.Bound = r.Bound
|
||||
}
|
||||
if f.Wait == 0 {
|
||||
f.Wait = r.Wait
|
||||
}
|
||||
f.Raise, f.Clear = func(s Status) { r.finding(s, true) }, func(s Status) { r.finding(s, false) }
|
||||
f.RaiseWaits, f.ClearWaits = func(s Status) { r.waits(s, true) }, func(s Status) { r.waits(s, false) }
|
||||
own := f.Changed
|
||||
f.Changed = func(key string, value json.RawMessage, deleted bool, at time.Time) {
|
||||
p.changed(key, value, deleted, at)
|
||||
if own != nil {
|
||||
own(key, value, deleted, at)
|
||||
}
|
||||
}
|
||||
r.pics[bucket] = p
|
||||
r.order = append(r.order, bucket)
|
||||
if r.adopted == nil {
|
||||
r.adopted = map[string]func(context.Context, []byte) error{}
|
||||
}
|
||||
r.adopted["mesh.seat."+seat+".event."+event] = hear
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// waits writes (raised) or clears the reader's view-waits finding of s (design 47 *A part with no view says it waits*).
|
||||
func (r *ProcessReader) waits(s Status, raised bool) {
|
||||
if raised {
|
||||
r.logf("%s has waited for %s's view %s since %s", r.Module, s.Owner, s.WaitsFor, s.WaitingSince.Format(time.RFC3339))
|
||||
}
|
||||
if r.Findings == nil {
|
||||
return
|
||||
}
|
||||
kv := r.Findings()
|
||||
if kv == nil {
|
||||
return
|
||||
}
|
||||
at := time.Now()
|
||||
if r.Now != nil {
|
||||
at = r.Now()
|
||||
}
|
||||
f := ViewWaitsFinding(r.Module, s, at)
|
||||
var err error
|
||||
if raised {
|
||||
err = kv.Put(f)
|
||||
} else {
|
||||
err = kv.Delete(f)
|
||||
}
|
||||
if err != nil {
|
||||
r.logf("views: the view-waits finding of %s is not written: %v", s.Tool, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Picture is the picture of the bucket the process used to bind, by its bus name (`<owner>_<state>`); nil when not
|
||||
// followed.
|
||||
func (r *ProcessReader) Picture(bucket string) *Picture {
|
||||
@@ -172,7 +254,17 @@ func (r *ProcessReader) Hear(ctx context.Context) func(jetstream.Msg) {
|
||||
for owner, m := range fs {
|
||||
views[owner] = Hear(ctx, m, r.logf)
|
||||
}
|
||||
r.mu.Lock()
|
||||
adopted := maps.Clone(r.adopted)
|
||||
r.mu.Unlock()
|
||||
return func(m jetstream.Msg) {
|
||||
if hear := adopted[m.Subject()]; hear != nil {
|
||||
if err := hear(ctx, m.Data()); err != nil {
|
||||
r.logf("views: %s is not taken: %v", m.Subject(), err)
|
||||
}
|
||||
_ = m.Ack()
|
||||
return
|
||||
}
|
||||
if owner, ok := OwnerOfEvent(m.Subject()); ok {
|
||||
if h := views[owner]; h != nil {
|
||||
h(m)
|
||||
@@ -550,7 +642,18 @@ func (r *ProcessReader) Start(ctx context.Context, js jetstream.JetStream, cred
|
||||
f.ConsumerMade(ctx, created)
|
||||
}
|
||||
})
|
||||
err := hub.Handle(pattern, r.Hear(ctx))
|
||||
hear := r.Hear(ctx)
|
||||
err := hub.Handle(pattern, hear)
|
||||
if !own {
|
||||
r.mu.Lock()
|
||||
subjects := slices.Sorted(maps.Keys(r.adopted))
|
||||
r.mu.Unlock()
|
||||
for _, subject := range subjects {
|
||||
if err == nil {
|
||||
err = hub.Handle(subject, hear)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil && own {
|
||||
err = hub.Start(ctx, js, cred)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user