diff --git a/go/clients/nodeengineclient/enginereads/enginereads.go b/go/clients/nodeengineclient/enginereads/enginereads.go index c86476b..16e2209 100644 --- a/go/clients/nodeengineclient/enginereads/enginereads.go +++ b/go/clients/nodeengineclient/enginereads/enginereads.go @@ -221,6 +221,10 @@ func NodeOf(subject string, data []byte) (string, bool, error) { type Keeper struct { // Call asks the verb; nil asks nothing (every node then reads as the fallback or the baseline). Call Caller + // Once asks the verb once (process.Conn.CallOn). When set, its fault keeps the fallback or the last statement at once, + // marked as not answering, while Call, the kept call, asks on until answered (novox/hq ADR 0668 §3): a part does not + // wait out busretry.Bound for a node whose engine answers nothing before it acts on what it last knew. + Once Caller // Fallback reads a node's record `.node` once where its engine did not answer the verb; nil reads none. Fallback func(node string) ([]string, bool) // Put writes a node's key of the part's state, Delete removes it; nil keeps the statements in memory only. @@ -292,10 +296,25 @@ func (k *Keeper) ask(node, why string) { delete(k.asking, node) k.mu.Unlock() }() + if k.Call != nil && k.Once != nil { + first := Ask(k.Once, node, k.now()) + if first.Answered { + k.take(first) + return + } + k.settle(first, why) + why += "; the kept call asks on after: " + first.Words + } s := Statement{Node: node, AskedAt: k.now().UTC(), From: FromNone, Words: "nothing asks the verb"} if k.Call != nil { s = Ask(k.Call, node, k.now()) } + k.settle(s, why) +} + +// settle takes s, read from the node's record in its place where the engine did not answer. +func (k *Keeper) settle(s Statement, why string) { + node := s.Node if s.From != FromVerb && k.Fallback != nil { if reads, ok := k.Fallback(node); ok { s.ManifestReads, s.From = slices.Clone(reads), FromRecord diff --git a/go/clients/nodeengineclient/enginereads/enginereads_test.go b/go/clients/nodeengineclient/enginereads/enginereads_test.go index 135c660..271d491 100644 --- a/go/clients/nodeengineclient/enginereads/enginereads_test.go +++ b/go/clients/nodeengineclient/enginereads/enginereads_test.go @@ -3,6 +3,7 @@ package enginereads import ( "context" "encoding/json" + "errors" "strings" "testing" "time" @@ -55,6 +56,21 @@ func TestTheAnswerAndTheEventAreJudged(t *testing.T) { if p := k.Present([]string{"ace", "g14"}); len(p["ace"]) != 1 || len(p["g14"]) != 1 || calls["ace"] != 2 || calls["g14"] != 1 { t.Errorf("after the bind: present %v, calls %v", p, calls) } + // Review round 1 (ADR 0668 §3): a node whose engine answers nothing reads as its record at the first fault of the call + // made once, while the kept call asks on; what the kept call answers then stands. + var during Statement + f := &Keeper{Sync: true, Fallback: func(string) ([]string, bool) { return []string{"r.s"}, true }, + Once: func(string, string, any) (json.RawMessage, error) { + return nil, errors.New("nats: no responders available for request") + }} + f.Call = func(node, _ string, _ any) (json.RawMessage, error) { + during = f.Statements()[node] + return json.RawMessage(`{"v":1,"node":"` + node + `","manifest-reads":["a.b"],"stated-at":"` + at + `"}`), nil + } + f.AskNode("g14", "the first view") + if during.From != FromRecord || during.Answered || f.Statements()["g14"].From != FromVerb { + t.Errorf("while the kept call asked g14 read %+v; after it %+v", during, f.Statements()["g14"]) + } if Verb != "manifest-reads" || Event != "manifest-reads-changed" || Consumes != "node-engine.manifest-reads-changed" || Invoke != "seat:node-engine.manifest-reads" { t.Errorf("the names are %s %s %s %s", Verb, Event, Consumes, Invoke) }