mesh_wait goes through the runner's approval gate (node-tools/internal/approval, Gate.Waited): a wait whose
subjects overlap a marked subject of any module the catalog half lists (the newest entry of every followed
branch, with the served entry of each module this runner serves) is an act of the runner's own module and
build (MESH_FINGERPRINT at its start), asked once on operator-channel as "Wait on <names> on <node>?", and
held, subscribing nothing but its ask's decided, until its timeout, its caller's going or the choice. On a
warrant it subscribes, its timeout running from then, and answers {answer, approval}. Before the marks are
read nothing is asked; a wait from a seat is never marked. The state fixture moves to meshtest so the
console's table can hold a gate.
Builds: design 34 every-event §34, §35, §36, §37; design 47 G lead answer A-168
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
197 lines
5.0 KiB
Go
197 lines
5.0 KiB
Go
package firstview
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
|
catalogseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
|
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
|
|
|
"github.com/novox/mesh-tools/node-tools/internal/approval"
|
|
)
|
|
|
|
// Findings is the runner's held finding writer, the one the gate writes its findings through too (design 46 7-approval-mark
|
|
// §4, §16, §19); nil when it was not made.
|
|
func (v *Views) Findings() approval.FindingWriter {
|
|
if v.deps.Findings == nil {
|
|
return nil
|
|
}
|
|
return v.deps.Findings
|
|
}
|
|
|
|
// ApprovalCatalog is the catalog half as the gate reads its marks (design 46 7-approval-mark §5): the entries the branch
|
|
// follower holds, an entry by fingerprint asked of `mesh-catalog.entry` when no branch lists it, and the seat definitions
|
|
// the seat list holds (defs).
|
|
func (v *Views) ApprovalCatalog(defs func() map[string]seatdef.Definition) approval.Catalog {
|
|
return approvalCatalog{v: v, defs: defs}
|
|
}
|
|
|
|
type approvalCatalog struct {
|
|
v *Views
|
|
defs func() map[string]seatdef.Definition
|
|
}
|
|
|
|
func (c approvalCatalog) Ready() error {
|
|
if c.v.branches == nil {
|
|
return errNotFollowed
|
|
}
|
|
return c.v.branches.Ready()
|
|
}
|
|
|
|
func (c approvalCatalog) Revision() string {
|
|
if c.v.branches == nil {
|
|
return ""
|
|
}
|
|
for _, branch := range c.v.branches.Followed() {
|
|
if cat, ok := c.v.branches.Catalog(branch); ok {
|
|
if half, ok := cat.View(); ok {
|
|
return strconv.FormatInt(half.Revision.Revision, 10)
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (c approvalCatalog) Served(fingerprint string) (catalogseat.CatalogEntry, bool, string) {
|
|
if c.v.branches == nil {
|
|
return catalogseat.CatalogEntry{}, false, errNotFollowed.Error()
|
|
}
|
|
e, state := c.v.branches.Resolve(context.Background(), fingerprint)
|
|
return e, state == meshcatalogclient.EntryAnswered, string(state)
|
|
}
|
|
|
|
func (c approvalCatalog) Newest(module string) (catalogseat.CatalogEntry, bool) {
|
|
if c.v.branches == nil {
|
|
return catalogseat.CatalogEntry{}, false
|
|
}
|
|
e, ok, err := c.v.branches.Entry(module)
|
|
return e, ok && err == nil
|
|
}
|
|
|
|
func (c approvalCatalog) Definitions() map[string]seatdef.Definition {
|
|
if c.defs == nil {
|
|
return nil
|
|
}
|
|
return c.defs()
|
|
}
|
|
|
|
func (c approvalCatalog) Entries() []catalogseat.CatalogEntry {
|
|
halves, err := c.v.Halves()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var out []catalogseat.CatalogEntry
|
|
for _, half := range halves {
|
|
for _, e := range half.Entries {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
type notFollowed struct{}
|
|
|
|
func (notFollowed) Error() string { return "the catalog half is not followed by this runner" }
|
|
|
|
var errNotFollowed error = notFollowed{}
|
|
|
|
// heardOne wakes every wait on the picture.
|
|
func (v *Views) heardOne() {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if v.heard != nil {
|
|
close(v.heard)
|
|
}
|
|
v.heard = make(chan struct{})
|
|
}
|
|
|
|
// heardNext is closed at the next event heard.
|
|
func (v *Views) heardNext() <-chan struct{} {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if v.heard == nil {
|
|
v.heard = make(chan struct{})
|
|
}
|
|
return v.heard
|
|
}
|
|
|
|
// WaitCatalog waits within the bound for the catalog half's first view of every watched branch (design 46 7-approval-mark
|
|
// §19), waking at each event the runner hears and at each first view answered; nil once it is read, else why not.
|
|
func (v *Views) WaitCatalog(within time.Duration) error {
|
|
c := approvalCatalog{v: v}
|
|
timer := time.NewTimer(within)
|
|
defer timer.Stop()
|
|
for {
|
|
next := v.heardNext()
|
|
err := c.Ready()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
var ready []<-chan struct{}
|
|
if v.branches != nil {
|
|
for _, branch := range v.branches.Followed() {
|
|
if cat, ok := v.branches.Catalog(branch); ok {
|
|
select {
|
|
case <-cat.Ready(): // read already: waits on nothing
|
|
default:
|
|
ready = append(ready, cat.Ready())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !waitAny(next, timer.C, ready) {
|
|
return c.Ready()
|
|
}
|
|
}
|
|
}
|
|
|
|
// waitAny waits for next or one of ready, true, or for the bound, false.
|
|
func waitAny(next <-chan struct{}, bound <-chan time.Time, ready []<-chan struct{}) bool {
|
|
woke := make(chan struct{}, 1)
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
for _, r := range ready {
|
|
go func(r <-chan struct{}) {
|
|
select {
|
|
case <-r:
|
|
woke <- struct{}{}
|
|
case <-stop:
|
|
}
|
|
}(r)
|
|
}
|
|
select {
|
|
case <-next:
|
|
case <-woke:
|
|
case <-bound:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// OnSeatHolder calls f whenever the assigner's seat-holders view changes for the seat (lead answer A-157: the gate asks
|
|
// mesh-hand-acts.warranted again when the hand-act seat's holder is heard again).
|
|
func (v *Views) OnSeatHolder(seat string, f func()) {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if v.onKey == nil {
|
|
v.onKey = map[string][]func(){}
|
|
}
|
|
k := "seat-holders " + seat
|
|
v.onKey[k] = append(v.onKey[k], f)
|
|
}
|
|
|
|
// heardKey calls the hearers of a key of an owner's state just changed: a seat's holders, under the seat's own key and
|
|
// its `<seat>.<node>` keys.
|
|
func (v *Views) heardKey(state, key string) {
|
|
seat, _, _ := strings.Cut(key, ".")
|
|
v.mu.Lock()
|
|
fs := append([]func(){}, v.onKey[state+" "+seat]...)
|
|
v.mu.Unlock()
|
|
for _, f := range fs {
|
|
go f()
|
|
}
|
|
}
|