Queue one state per call for the bus, bounded far above a burst, so a burst of read calls no longer drops call records (issue 774)
At 17:44:53 the controller said 30 calls (tools, nodes, modules) were "kept in memory only: the bus is not taking calls' records". No write had failed (no "could not be kept" line): a burst of about 200 read calls in one second queued a begin and a finish each into a channel of 256 while the one writer put them one by one, and the channel overflowed. The queue now holds the latest state of each waiting call (a finish replaces its own waiting begin, as only the last state is kept anyway), in first-queued order, bounded at 8192 calls; the writer takes the whole queue at each wake. A burst now costs one write per call and drops nothing. Quick fix: a larger channel. Thorough fix: coalesce by call, so the writes a burst asks are halved and the bound is spent on calls, not states. Chose thorough; both are small. Research 072 piece: §2's "read calls said" row: the calls bucket is the controller's own state on the bus, kept from events (each call's change), never re-read.
This commit is contained in:
@@ -254,8 +254,7 @@ func TestAHandoverRefusalIsMarkedRetryable(t *testing.T) {
|
||||
// who may call the seat, and a token is the one-time right to become a machine of the mesh.
|
||||
func TestAJoinTokenIsShownToItsCallerAndNotKept(t *testing.T) {
|
||||
l, a := NewCallLog(), newAnswers(t)
|
||||
writes := make(chan Call, 4)
|
||||
l.writes = writes
|
||||
l.woken = make(chan struct{}, 1) // queued for the keeper, and nothing takes them: read below
|
||||
l.serveCall("mesh-controller", "token", json.RawMessage(`{"new":"laptop"}`), "_INBOX.x.1",
|
||||
func(context.Context, json.RawMessage) (any, error) { return "token for laptop: s3cret-join", nil },
|
||||
a.respond, nil)
|
||||
@@ -266,8 +265,7 @@ func TestAJoinTokenIsShownToItsCallerAndNotKept(t *testing.T) {
|
||||
if len(recent) != 1 || strings.Contains(string(recent[0].Answer), "s3cret-join") {
|
||||
t.Fatalf("the token was kept in memory: %+v", recent)
|
||||
}
|
||||
close(writes)
|
||||
for c := range writes {
|
||||
for _, c := range l.queued.take() {
|
||||
if strings.Contains(string(c.Answer), "s3cret-join") {
|
||||
t.Fatalf("the token was sent to be kept on the bus: %s", c.Answer)
|
||||
}
|
||||
@@ -276,3 +274,33 @@ func TestAJoinTokenIsShownToItsCallerAndNotKept(t *testing.T) {
|
||||
|
||||
// A verb whose handler panics answers an error, and the controller serves the next call (review of
|
||||
// novox/hq issue 327: read verbs are answered in the serving process now).
|
||||
|
||||
// Sweep 2026-10-30 (issue 774): a burst of calls never loses a record while the keeper is slow: each call waits once,
|
||||
// with its latest state, and every call of a burst far over the old queue of 256 is kept with its last state.
|
||||
func TestABurstOfCallsIsKeptWithEachCallsLastState(t *testing.T) {
|
||||
var q callQueue
|
||||
for i := range 2000 {
|
||||
id := fmt.Sprintf("call-%d", i)
|
||||
if !q.put(Call{ID: id, State: CallRunning}) || !q.put(Call{ID: id, State: CallAnswered}) {
|
||||
t.Fatalf("%s was refused by a queue of %d", id, callQueueBound)
|
||||
}
|
||||
}
|
||||
got := q.take()
|
||||
if len(got) != 2000 {
|
||||
t.Fatalf("%d calls wait, want 2000", len(got))
|
||||
}
|
||||
for i, c := range got {
|
||||
if c.ID != fmt.Sprintf("call-%d", i) || c.State != CallAnswered {
|
||||
t.Fatalf("call %d waits as %+v", i, c)
|
||||
}
|
||||
}
|
||||
if len(q.take()) != 0 {
|
||||
t.Fatal("taken calls wait again")
|
||||
}
|
||||
for i := range callQueueBound {
|
||||
q.put(Call{ID: fmt.Sprint(i)})
|
||||
}
|
||||
if q.put(Call{ID: "one too many"}) {
|
||||
t.Fatal("the queue grew past its bound")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user