S5: the whole node reads its seat list after a holder that re-served
The live case of ace, in process against a real bus: the stand-in of the seat mesh-catalog answers as nothing serves it (no responders) while the node's reader first asks, then serves; the same reader reads the list within the bus's retry bound, with no event of the seat and no hand act (issue 1966, ADR 0636 §8). The test of an unread list now reads the reason that names the bootstrapped seats. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/ed25519"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -72,7 +74,10 @@ type itBed struct {
|
||||
seatSilent atomic.Bool
|
||||
// seatAsked counts the node's asks of the stand-in seat, answered or refused: the node reached the bus and asked.
|
||||
seatAsked atomic.Int32
|
||||
findings nats.KeyValue
|
||||
// seatAway makes the stand-in answer as the bus answers a subject nothing serves (no responders), as while the seat's
|
||||
// holder re-serves (issue 1966).
|
||||
seatAway atomic.Bool
|
||||
findings nats.KeyValue
|
||||
}
|
||||
|
||||
// aServer is an embedded bus with JetStream and two users: the test's, unrestricted, and the node's, granted exactly what
|
||||
@@ -918,6 +923,10 @@ func (b *itBed) standInSeat() {
|
||||
}
|
||||
_ = json.Unmarshal(m.Data, &q)
|
||||
b.seatAsked.Add(1)
|
||||
if b.seatAway.Load() {
|
||||
_ = m.RespondMsg(&nats.Msg{Header: nats.Header{"Status": []string{"503"}}}) // the bus's no responders
|
||||
return
|
||||
}
|
||||
var result any
|
||||
switch {
|
||||
case b.seatSilent.Load():
|
||||
@@ -1002,7 +1011,14 @@ func TestNatsANodeThatCannotReadTheSeatListActsOnNothingNew(t *testing.T) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
said := strings.Join(b.said, "\n")
|
||||
return strings.Contains(said, selfcompose.WaitsForTheSeatListOwn) && strings.Contains(said, selfcompose.WaitsForTheSeatList)
|
||||
// web's reason names the bootstrapped seats the node holds (issue 1966).
|
||||
var boot []string
|
||||
for _, d := range seatlist.Bootstrapped() {
|
||||
boot = append(boot, d.Seat)
|
||||
}
|
||||
sort.Strings(boot)
|
||||
return strings.Contains(said, selfcompose.WaitsForTheSeatListOwn) &&
|
||||
strings.Contains(said, "web: "+fmt.Sprintf(selfcompose.WaitsForTheSeatListHeld, len(boot), strings.Join(boot, ", ")))
|
||||
})
|
||||
if b.file("web") != "" {
|
||||
t.Fatalf("the node applied web before it read the seat list: %q", b.file("web"))
|
||||
@@ -1047,6 +1063,10 @@ func TestNatsANodeThatCannotReadTheSeatListActsOnNothingNew(t *testing.T) {
|
||||
|
||||
// The first read of the seat list ends the wait whether or not it changed what the reader held: Start returns at once, and
|
||||
// the node waits on the reader's Ready before it says the read is over (ADR 0636 §1).
|
||||
//
|
||||
// Issue 1966, the live case of ace: the seat's holder re-serves while the node starts, so the node's reader is answered as
|
||||
// nothing serves the seat (no responders); once the holder serves again the reader reads the list within the bus's retry
|
||||
// bound, with no event of the seat and no hand act.
|
||||
func TestNatsTheFirstReadOfTheSeatListEndsTheNodesWait(t *testing.T) {
|
||||
clock := &fiveMinutes{}
|
||||
was := seatWaitClock
|
||||
@@ -1054,8 +1074,17 @@ func TestNatsTheFirstReadOfTheSeatListEndsTheNodesWait(t *testing.T) {
|
||||
t.Cleanup(func() { seatWaitClock = was })
|
||||
|
||||
b := newItBedAs(t, nodegrant.For(itNode, "it2.mailer.domain"))
|
||||
b.seatAway.Store(true)
|
||||
stop, _ := h8(t, b, false)
|
||||
defer stop()
|
||||
var st *seatListState
|
||||
within(t, 5*time.Second, "the node started no reader of the seat list", func() bool { st = seatListNow.Load(); return st != nil })
|
||||
asked := b.seatAsked.Load()
|
||||
within(t, 10*time.Second, "the node's reader did not ask the seat while nothing served it", func() bool {
|
||||
now := st.reader.Status()
|
||||
return b.seatAsked.Load() > asked || (!now.Reading && !now.Heard)
|
||||
})
|
||||
b.seatAway.Store(false)
|
||||
within(t, 30*time.Second, "the node did not end its wait at the first read of the seat list", func() bool {
|
||||
st := seatListNow.Load()
|
||||
if st == nil {
|
||||
@@ -1065,4 +1094,7 @@ func TestNatsTheFirstReadOfTheSeatListEndsTheNodesWait(t *testing.T) {
|
||||
waiting, _ := st.waiter.Waiting()
|
||||
return st.reader.Status().Heard && !waiting
|
||||
})
|
||||
if seatListNow.Load() != st {
|
||||
t.Error("the list was read by the reader of a run begun since, not by the one that met no responders")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user