Revert the revert of G's mesh-sdk merge (#191): G's sdk stands again over reader release 3
Main reverted G's half merge (#189, reverted by8afc3ebin #191) and then took the reader release's S1-S5, which copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's headf30dee54only by those and the release's two tests (objects cap option, seat states and stream kinds). Builds: design 47 G (the milestone's sdk part, as atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
+369
-37
@@ -14,6 +14,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -35,7 +36,7 @@ type ProcessReader struct {
|
||||
Call func(key string, body any) (json.RawMessage, error)
|
||||
// Findings is the reader's own state `findings`, where the condition keeper raises and clears the condition of a
|
||||
// first view unanswered past its bound (ADR 0615 §6); nil says it in the log only.
|
||||
Findings func() FindingWriter
|
||||
Findings func() WireWriter
|
||||
Logf func(string, ...any)
|
||||
// Now, Bound and Wait are for tests: zero are the clock, Bound and five seconds.
|
||||
Now func() time.Time
|
||||
@@ -55,6 +56,29 @@ 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.
|
||||
// Several followers adopt one event (two branches' views of the catalog half), each hearing it (design 47 G §532).
|
||||
adopted map[string][]adoptedHear
|
||||
// seats are the views found by their seat (FollowSeat), by their picture's bus name <seat>_<state> (design 47 G §717).
|
||||
seats map[string]*seatFollow
|
||||
|
||||
// After Start and Ask (design 47 G §532): the hub the reader's events come through when it is the module's (Hub) and
|
||||
// its handler, the time the bus made the consumer, the asks' life and Said, and of each first view begun, the cancel
|
||||
// of its asks, by bucket; status is the state `first-views` each status is kept in.
|
||||
hub *process.EventHub
|
||||
handle func(jetstream.Msg)
|
||||
created time.Time
|
||||
ctx context.Context
|
||||
said func(Status)
|
||||
asked bool
|
||||
begun map[string]context.CancelFunc
|
||||
status *process.AwaitedBucket
|
||||
}
|
||||
|
||||
// adoptedHear is one adopted follower's handler of its seat event, by its picture's bucket.
|
||||
type adoptedHear struct {
|
||||
bucket string
|
||||
hear func(context.Context, []byte) error
|
||||
}
|
||||
|
||||
func (r *ProcessReader) logf(format string, args ...any) {
|
||||
@@ -66,19 +90,28 @@ func (r *ProcessReader) logf(format string, args ...any) {
|
||||
// Follow adds the owner's state to follow, as the owner declares it (design 25 § 12.2): tool is its ordinary read tool
|
||||
// of state as the reader's `invokes` names it (`<owner>.<tool>`, from the owner's manifest's tools). The owner's changes
|
||||
// are ordinary events (`<owner>.views.<state>`), which the reader's manifest `consumes` one entry per followed state.
|
||||
// views names no owner: the caller gives it. Call it before Start.
|
||||
// views names no owner: the caller gives it. Called after Start, the view takes its first view at once and then follows
|
||||
// (design 47 G §532).
|
||||
func (r *ProcessReader) Follow(owner, state, tool string) (*Picture, error) {
|
||||
p, fresh, err := r.follow(owner, state, tool)
|
||||
if err == nil && fresh {
|
||||
r.begin(p.bucket)
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (r *ProcessReader) follow(owner, state, tool string) (*Picture, bool, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if owner == "" || state == "" || !strings.HasPrefix(tool, owner+".") {
|
||||
return nil, fmt.Errorf("%s follows %s's %s by its read tool <owner>.<tool>, not %q", r.Module, owner, state, tool)
|
||||
return nil, false, fmt.Errorf("%s follows %s's %s by its read tool <owner>.<tool>, not %q", r.Module, owner, state, tool)
|
||||
}
|
||||
if r.pics == nil {
|
||||
r.pics = map[string]*Picture{}
|
||||
}
|
||||
bucket := owner + "_" + state
|
||||
if p := r.pics[bucket]; p != nil {
|
||||
return p, nil
|
||||
return p, false, nil
|
||||
}
|
||||
p := &Picture{bucket: bucket, ctx: context.Background()}
|
||||
call := r.Call
|
||||
@@ -94,13 +127,173 @@ func (r *ProcessReader) Follow(owner, state, tool string) (*Picture, error) {
|
||||
return call(key, m)
|
||||
}, tool)
|
||||
p.f = &Follower{Owner: owner, Tool: tool, State: state, Ask: ask, Logf: r.Logf, Now: r.Now, Bound: r.Bound, Wait: r.Wait,
|
||||
Raise: func(s Status) { r.finding(s, true) }, Clear: func(s Status) { r.finding(s, false) },
|
||||
Changed: p.changed}
|
||||
Raise: func(s Status) {
|
||||
if r.holds(p) {
|
||||
r.finding(s, true)
|
||||
}
|
||||
}, Clear: func(s Status) { r.finding(s, false) },
|
||||
Changed: func(key string, value json.RawMessage, deleted bool, at time.Time) {
|
||||
p.changed(key, value, deleted, at)
|
||||
r.seatsChanged() // a seat's view reads its holder again (§717)
|
||||
}}
|
||||
r.pics[bucket] = p
|
||||
r.order = append(r.order, bucket)
|
||||
return p, true, nil
|
||||
}
|
||||
|
||||
// holds says whether p is still a picture of the reader: a dropped view writes no finding after its drop cleared them.
|
||||
func (r *ProcessReader) holds(p *Picture) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.pics[p.bucket] == p
|
||||
}
|
||||
|
||||
// FollowBranch is Follow for a state kept per branch: the view of one branch is named `<owner>.<state>.<branch>` (the
|
||||
// branch escaped as a key's name is, BranchState) and its picture `<owner>_<state>.<branch>`, so a process follows
|
||||
// several branches of one state, each its own view (design 47 G §479). Called after Start, it begins at once (§532).
|
||||
func (r *ProcessReader) FollowBranch(owner, state, branch, tool string) (*Picture, error) {
|
||||
return r.Follow(owner, BranchState(state, branch), tool)
|
||||
}
|
||||
|
||||
// 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; a state followed already
|
||||
// is refused. Adopted after Start, as a part adopts a watched branch added while it runs, the view takes its first view
|
||||
// at once and then follows (design 47 G §532).
|
||||
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")
|
||||
}
|
||||
p, subject, err := r.adopt(f, seat, event, hear)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.mu.Lock()
|
||||
hub, handle := r.hub, r.handle
|
||||
r.mu.Unlock()
|
||||
if subject != "" && hub != nil {
|
||||
// one subscription per event, with every further part (design 47 G §532)
|
||||
if err := hub.Handle(subject+".>", handle); err != nil {
|
||||
r.logf("views: %s's event %s is not heard (%v): the view %s is its first view's alone, asked again by hand", r.Module, subject, err, p.bucket)
|
||||
}
|
||||
}
|
||||
r.begin(p.bucket)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// AdoptEvent hears a seat's event on the one subscription the followers adopted on that event share, with no picture and no
|
||||
// first view (design 47 G A-185, A-199): hear is handed each message of the event beside every adopted follower's handler,
|
||||
// and the message is acknowledged, unless hear fails: then it is left unacknowledged and the bus asks again (A-222). The subscription is made at once when no follower made it, so a part that follows no
|
||||
// view of the seat still hears the event; adopted before Start, it is made at Start.
|
||||
func (r *ProcessReader) AdoptEvent(seat, event string, hear func(context.Context, []byte) error) error {
|
||||
if seat == "" || event == "" || hear == nil {
|
||||
return errors.New("views: an adopted event names its seat, its event and its handler")
|
||||
}
|
||||
subject := "mesh.seat." + seat + ".event." + event
|
||||
r.mu.Lock()
|
||||
if r.adopted == nil {
|
||||
r.adopted = map[string][]adoptedHear{}
|
||||
}
|
||||
fresh := len(r.adopted[subject]) == 0
|
||||
r.adopted[subject] = append(r.adopted[subject], adoptedHear{hear: hear})
|
||||
hub, handle := r.hub, r.handle
|
||||
r.mu.Unlock()
|
||||
if fresh && hub != nil {
|
||||
// one subscription per event, with every further part (design 47 G §532)
|
||||
if err := hub.Handle(subject+".>", handle); err != nil {
|
||||
r.logf("views: %s's event %s is not heard (%v)", r.Module, subject, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adopt is Adopt's part under the lock; subject is the event's subject when no follower adopted it before.
|
||||
func (r *ProcessReader) adopt(f *Follower, seat, event string, hear func(context.Context, []byte) error) (*Picture, string, error) {
|
||||
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) {
|
||||
if r.holds(p) {
|
||||
r.finding(s, true)
|
||||
}
|
||||
}, func(s Status) { r.finding(s, false) }
|
||||
f.RaiseWaits, f.ClearWaits = func(s Status) {
|
||||
if r.holds(p) {
|
||||
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.seatsChanged() // a seat's view reads its holder again (§717)
|
||||
}
|
||||
r.pics[bucket] = p
|
||||
r.order = append(r.order, bucket)
|
||||
if r.adopted == nil {
|
||||
r.adopted = map[string][]adoptedHear{}
|
||||
}
|
||||
subject := "mesh.seat." + seat + ".event." + event
|
||||
fresh := len(r.adopted[subject]) == 0
|
||||
r.adopted[subject] = append(r.adopted[subject], adoptedHear{bucket: bucket, hear: hear})
|
||||
if !fresh {
|
||||
subject = ""
|
||||
}
|
||||
return p, subject, 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 = f.put(kv)
|
||||
} else {
|
||||
err = f.drop(kv)
|
||||
}
|
||||
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 {
|
||||
@@ -113,6 +306,9 @@ func (r *ProcessReader) Picture(bucket string) *Picture {
|
||||
// as buildrun.FollowPlacement takes it); any other bucket is refused, for the reader reads its owners' states by their
|
||||
// first views alone.
|
||||
func (r *ProcessReader) KeyValue(_ context.Context, bucket string) (jetstream.KeyValue, error) {
|
||||
if p := r.seatPicture(bucket); p != nil {
|
||||
return p, nil
|
||||
}
|
||||
if p := r.Picture(bucket); p != nil {
|
||||
return p, nil
|
||||
}
|
||||
@@ -145,12 +341,12 @@ func (r *ProcessReader) finding(s Status, raised bool) {
|
||||
if r.Now != nil {
|
||||
at = r.Now()
|
||||
}
|
||||
f := UnansweredFinding(r.Module, s, at)
|
||||
f := unansweredFinding(r.Module, s, at)
|
||||
var err error
|
||||
if raised {
|
||||
err = kv.Put(f)
|
||||
err = f.put(kv)
|
||||
} else {
|
||||
err = kv.Delete(f)
|
||||
err = f.drop(kv)
|
||||
}
|
||||
if err != nil {
|
||||
r.logf("views: the finding of the first view %s is not written: %v", s.Tool, err)
|
||||
@@ -159,26 +355,42 @@ func (r *ProcessReader) finding(s Status, raised bool) {
|
||||
|
||||
// Hear is the handler of the module's durable consumer on the shared events stream (cred.EventConsumer on
|
||||
// process.EventsStream): an owner's views event (`mesh.mod.<owner>.event.views.<state>`) goes to the follower of that
|
||||
// owner's state, any other message to Other.
|
||||
// owner's state, any other message to Other. The followers are read at each message, so a view followed or adopted
|
||||
// after Start hears its events and a dropped one no longer does (design 47 G §532).
|
||||
func (r *ProcessReader) Hear(ctx context.Context) func(jetstream.Msg) {
|
||||
fs := map[string]map[string]*Follower{}
|
||||
for _, f := range r.Followers() {
|
||||
if fs[f.Owner] == nil {
|
||||
fs[f.Owner] = map[string]*Follower{}
|
||||
}
|
||||
fs[f.Owner][f.State] = f
|
||||
}
|
||||
views := map[string]func(jetstream.Msg){}
|
||||
for owner, m := range fs {
|
||||
views[owner] = Hear(ctx, m, r.logf)
|
||||
}
|
||||
return func(m jetstream.Msg) {
|
||||
if owner, ok := OwnerOfEvent(m.Subject()); ok {
|
||||
if h := views[owner]; h != nil {
|
||||
h(m)
|
||||
return
|
||||
r.mu.Lock()
|
||||
hears := adoptedHears(r.adopted, m.Subject())
|
||||
var owned map[string]*Follower
|
||||
if owner, ok := OwnerOfEvent(m.Subject()); ok && hears == nil {
|
||||
for _, b := range r.order {
|
||||
if f := r.pics[b].f; f.Owner == owner {
|
||||
if owned == nil {
|
||||
owned = map[string]*Follower{}
|
||||
}
|
||||
owned[f.State] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
r.mu.Unlock()
|
||||
if hears != nil {
|
||||
failed := false
|
||||
for _, h := range hears {
|
||||
if err := h.hear(ctx, m.Data()); err != nil {
|
||||
r.logf("views: %s is not taken: %v", m.Subject(), err)
|
||||
// an event adopted with no picture is asked again by the bus (design 47 G A-222)
|
||||
failed = failed || h.bucket == ""
|
||||
}
|
||||
}
|
||||
if !failed {
|
||||
_ = m.Ack()
|
||||
}
|
||||
return
|
||||
}
|
||||
if owned != nil {
|
||||
Hear(ctx, owned, r.logf)(m)
|
||||
return
|
||||
}
|
||||
if r.Other != nil {
|
||||
r.Other(m)
|
||||
return
|
||||
@@ -201,18 +413,100 @@ func (r *ProcessReader) Owners() []string {
|
||||
}
|
||||
|
||||
// Ask asks every first view until answered, each on a goroutine of its own, so the process starts at once and waits for
|
||||
// an owner that is down, saying so (ADR 0615 §6). Said, when set, keeps each status (KeepStatus).
|
||||
// an owner that is down, saying so (ADR 0615 §6). Said, when set, keeps each status (KeepStatus). A view followed or
|
||||
// adopted after it is asked at once (design 47 G §532).
|
||||
func (r *ProcessReader) Ask(ctx context.Context, said func(Status)) {
|
||||
for _, f := range r.Followers() {
|
||||
if said != nil {
|
||||
f.Said = said
|
||||
}
|
||||
go func() {
|
||||
if err := f.Start(ctx); err != nil {
|
||||
r.logf("the first view %s was not answered: %v", f.Tool, err)
|
||||
}
|
||||
}()
|
||||
r.mu.Lock()
|
||||
r.ctx, r.said, r.asked = ctx, said, true
|
||||
buckets := slices.Clone(r.order)
|
||||
seats := slices.Collect(maps.Values(r.seats))
|
||||
r.mu.Unlock()
|
||||
for _, b := range buckets {
|
||||
r.begin(b)
|
||||
}
|
||||
for _, sw := range seats {
|
||||
r.runSeat(sw)
|
||||
}
|
||||
}
|
||||
|
||||
// begin asks bucket's first view once Ask ran, on a goroutine of its own, until answered or the view is dropped.
|
||||
func (r *ProcessReader) begin(bucket string) {
|
||||
r.mu.Lock()
|
||||
p := r.pics[bucket]
|
||||
if !r.asked || p == nil || r.begun[bucket] != nil {
|
||||
r.mu.Unlock()
|
||||
return
|
||||
}
|
||||
if r.begun == nil {
|
||||
r.begun = map[string]context.CancelFunc{}
|
||||
}
|
||||
ctx, cancel := context.WithCancel(r.ctx)
|
||||
r.begun[bucket] = cancel
|
||||
f, said, created := p.f, r.said, r.created
|
||||
r.mu.Unlock()
|
||||
if said != nil {
|
||||
f.Said = func(s Status) {
|
||||
if r.holds(p) {
|
||||
said(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !created.IsZero() {
|
||||
f.ConsumerMade(ctx, created)
|
||||
}
|
||||
go func() {
|
||||
if err := f.Start(ctx); err != nil && ctx.Err() == nil {
|
||||
r.logf("the first view %s was not answered: %v", f.Tool, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Drop ends the view of owner's state, as a part drops a watched branch removed while it runs (design 47 G §532, §614):
|
||||
// it asks it no more and hears none of its events, its status leaves the state `first-views` and so its row leaves
|
||||
// `<module>_first_views`, and every finding it raised (its unanswered first view, its view-waits) is cleared through
|
||||
// the reader's findings, the drop and each cleared finding said in the log. A state not followed is an error.
|
||||
func (r *ProcessReader) Drop(owner, state string) error {
|
||||
bucket := owner + "_" + state
|
||||
r.mu.Lock()
|
||||
p := r.pics[bucket]
|
||||
if p == nil {
|
||||
r.mu.Unlock()
|
||||
return fmt.Errorf("%s does not follow %s's %s", r.Module, owner, state)
|
||||
}
|
||||
cancel := r.begun[bucket]
|
||||
delete(r.pics, bucket)
|
||||
delete(r.begun, bucket)
|
||||
r.order = slices.DeleteFunc(r.order, func(b string) bool { return b == bucket })
|
||||
for subject, hs := range r.adopted {
|
||||
if hs = slices.DeleteFunc(hs, func(h adoptedHear) bool { return h.bucket == bucket }); len(hs) == 0 {
|
||||
delete(r.adopted, subject)
|
||||
} else {
|
||||
r.adopted[subject] = hs
|
||||
}
|
||||
}
|
||||
status := r.status
|
||||
r.mu.Unlock()
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
r.logf("views: %s drops its view %s.%s", r.Module, owner, state)
|
||||
if status != nil {
|
||||
if kv := status.KV(); kv != nil {
|
||||
if err := kv.Delete(context.Background(), StatusKey(owner, state)); err != nil {
|
||||
r.logf("views: the status of the dropped view %s.%s is not removed from %s: %v", owner, state, StatusState, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
s := p.f.Status()
|
||||
if s.Raised {
|
||||
r.finding(s, false)
|
||||
r.logf("views: %s clears its finding %s of the dropped view %s.%s", r.Module, UnansweredKind, owner, state)
|
||||
}
|
||||
if s.WaitsRaised {
|
||||
r.waits(s, false)
|
||||
r.logf("views: %s clears its finding %s of the dropped view %s.%s", r.Module, ViewWaitsKind, owner, state)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ready is closed once every followed state had its first view.
|
||||
@@ -546,11 +840,33 @@ func (r *ProcessReader) Start(ctx context.Context, js jetstream.JetStream, cred
|
||||
hub, pattern, own = process.NewEventHub(r.logf), ">", true
|
||||
}
|
||||
hub.OnBound(func(created time.Time) {
|
||||
r.mu.Lock()
|
||||
r.created = created
|
||||
r.mu.Unlock()
|
||||
for _, f := range r.Followers() {
|
||||
f.ConsumerMade(ctx, created)
|
||||
}
|
||||
})
|
||||
err := hub.Handle(pattern, r.Hear(ctx))
|
||||
hear := r.Hear(ctx)
|
||||
err := hub.Handle(pattern, hear)
|
||||
r.mu.Lock()
|
||||
r.status = status
|
||||
if !own {
|
||||
// an event adopted after the start is handled on the module's hub (Adopt); the reader's own hub takes every one
|
||||
r.hub, r.handle = hub, hear
|
||||
}
|
||||
r.mu.Unlock()
|
||||
if !own {
|
||||
r.mu.Lock()
|
||||
subjects := slices.Sorted(maps.Keys(r.adopted))
|
||||
r.mu.Unlock()
|
||||
for _, subject := range subjects {
|
||||
// one subscription per event, with every further part (design 47 G §532)
|
||||
if err == nil {
|
||||
err = hub.Handle(subject+".>", hear)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil && own {
|
||||
err = hub.Start(ctx, js, cred)
|
||||
}
|
||||
@@ -561,7 +877,8 @@ func (r *ProcessReader) Start(ctx context.Context, js jetstream.JetStream, cred
|
||||
var said func(Status)
|
||||
if status != nil {
|
||||
status.Ready(func(kv jetstream.KeyValue) {
|
||||
if err := HearAgain(ctx, kv, r.Followers(), r.logf); err != nil {
|
||||
KeepOwnBuild(ctx, kv, process.OwnBuild(), r.logf)
|
||||
if err := hearAgain(ctx, kv, r.Followers, r.logf); err != nil {
|
||||
r.logf("views: the hand asks of %s's first views are not heard (%v) until it starts again", r.Module, err)
|
||||
}
|
||||
})
|
||||
@@ -573,3 +890,18 @@ func (r *ProcessReader) Start(ctx context.Context, js jetstream.JetStream, cred
|
||||
}
|
||||
r.Ask(ctx, said)
|
||||
}
|
||||
|
||||
// adoptedHears are the handlers of every follower adopted on the event whose subject is subject or has further parts
|
||||
// after it (`<event>.<module>.<branch>.<commit or group>`, design 47 G §521, §532), or nil.
|
||||
func adoptedHears(adopted map[string][]adoptedHear, subject string) []adoptedHear {
|
||||
if h := adopted[subject]; h != nil {
|
||||
return h
|
||||
}
|
||||
for i := strings.LastIndex(subject, "."); i > 0; i = strings.LastIndex(subject, ".") {
|
||||
subject = subject[:i]
|
||||
if h := adopted[subject]; h != nil {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user