Ask again at start a merge ask whose refusal the teller did not hear, since the router keeps no record of a refused ask (issue 1610, review round 1)
mesh/merge-gate head 037bf1ef361b merged into trunk b04ee764739a — pass: builds mesh-delivery-teller → novox; no bus step; every machine composes with the …
mesh/repo-check head 037bf1ef361b merged into trunk b04ee764739a — pass: its merge-check.sh passed
mesh/delivery delivered

This commit is contained in:
2026-10-21 16:45:31 +02:00
parent 38116fe00a
commit 037bf1ef36
3 changed files with 130 additions and 9 deletions
@@ -34,6 +34,7 @@ func (b *bed) askOf(t *testing.T, n int, rev uint64) (string, delivery.Ask) {
func (b *bed) refuse(t *testing.T, id, words string) { func (b *bed) refuse(t *testing.T, id, words string) {
t.Helper() t.Helper()
delete(b.seenIDs, id) delete(b.seenIDs, id)
delete(b.records, id)
b.deliver(t, asks.Warrant{Ask: id, Asker: delivery.TellerModule, Outcome: asks.OutcomeRefused, Words: words, At: now}) b.deliver(t, asks.Warrant{Ask: id, Asker: delivery.TellerModule, Outcome: asks.OutcomeRefused, Words: words, At: now})
} }
@@ -186,3 +187,66 @@ func TestAnAskRefusedForItsWordsIsSaidAtOnceAndNotAskedAgain(t *testing.T) { //
t.Errorf("asks = %v: an ask refused for its words was asked again", got) t.Errorf("asks = %v: an ask refused for its words was asked again", got)
} }
} }
// restart is a new teller on the same buckets and the same bus (its message ids still in the bus's duplicate window), and
// the router's records as they stand; the watch hands over the gate's keys, then says it has handed over all.
func (b *bed) restart(t *testing.T) *bed {
t.Helper()
n := newBed(t)
n.store, n.answers, n.gate, n.records, n.msgIDs, n.seenIDs = b.store, b.answers, b.gate, b.records, b.msgIDs, b.seenIDs
n.client.State = b.store
n.t.Store, n.t.Answers, n.t.Gate = b.store, b.answers, b.gate
for key, value := range b.gate {
n.t.OnAsk(key, 1, value)
}
n.t.OnCurrent()
return n
}
// Review of 38116fe0 (reviewer-1610): an ask asked again whose refusal the teller never heard (it was away; the router's
// warrants come on a plain subscription) is asked again at the next start: the router keeps no record of a refused ask,
// so no record there means it was not taken.
func TestAnAskAskedAgainWhoseRefusalWasMissedIsAskedAgainAtStart(t *testing.T) { // 1610/AC4
b := newBed(t)
b.askOf(t, 1, 1)
_, held := b.askOf(t, 2, 2)
b.refuse(t, held.Ask.ID, roomRefusal)
_, other := b.askOf(t, 3, 3)
b.expire(t, other)
b.records[other.Ask.ID] = `{"state":"expired"}`
if got := b.askedIDs(); got[len(got)-1] != held.Ask.ID {
t.Fatalf("asks = %v, want %s asked again", got, held.Ask.ID)
}
// Refused again, and the teller does not hear it: the router forgets it.
delete(b.seenIDs, held.Ask.ID)
delete(b.records, held.Ask.ID)
n := b.restart(t)
got := n.askedIDs()
if len(got) != 1 || got[0] != held.Ask.ID {
t.Fatalf("asks after the start = %v, want %s asked again; logs:\n%s", got, held.Ask.ID, n.logs.String())
}
if len(n.busDropped) != 0 {
t.Errorf("the bus dropped %v as a duplicate", n.busDropped)
}
}
// The same for a first asking: kept open by the teller, its refusal never heard, and no record at the router.
func TestAFirstAskingWhoseRefusalWasMissedIsAskedAgainAtStart(t *testing.T) { // 1610/AC4
b := newBed(t)
_, held := b.askOf(t, 1, 1)
delete(b.seenIDs, held.Ask.ID) // refused for room, unheard: the router keeps no record of it
delete(b.records, held.Ask.ID)
n := b.restart(t)
got := n.askedIDs()
if len(got) != 1 || got[0] != held.Ask.ID {
t.Fatalf("asks after the start = %v, want %s asked again; logs:\n%s", got, held.Ask.ID, n.logs.String())
}
if len(n.busDropped) != 0 {
t.Errorf("the bus dropped %v as a duplicate", n.busDropped)
}
// An ask the router holds (it took the one asked again) is not asked again.
m := n.restart(t)
if got := m.askedIDs(); len(got) != 0 {
t.Errorf("an ask the router holds was asked again: %v", got)
}
}
@@ -269,6 +269,12 @@ func (t *Teller) catchUp(id string) {
return return
} }
if w == nil { if w == nil {
if state == "" {
// The router holds no record of an ask the teller holds open: it refused it (a refused ask is not kept) while
// the teller was away and did not hear it, or has not read it yet. It waits for room, and is asked again at the
// start or at the next end; the router drops a second ask under an id it holds (review of 38116fe0).
t.unheard(id)
}
return return
} }
t.logf("the ask %s is %s at the router: its warrant is handled now", id, state) t.logf("the ask %s is %s at the router: its warrant is handled now", id, state)
@@ -402,7 +408,7 @@ func (t *Teller) said(h asker.Handled, w asks.Warrant, err error) {
if h == asker.Acted || h == asker.Recorded || h == asker.Done { if h == asker.Acted || h == asker.Recorded || h == asker.Done {
// One of the teller's asks ended at the router, so it has room for one more: the first ask waiting is asked again. // One of the teller's asks ended at the router, so it has room for one more: the first ask waiting is asked again.
t.endWait(w.Ask) t.endWait(w.Ask)
t.askWaiting() t.askWaiting(1)
} }
} }
@@ -500,6 +506,8 @@ type waitingAsk struct {
ID string `json:"id"` ID string `json:"id"`
Refusals int `json:"refusals"` Refusals int `json:"refusals"`
Asked bool `json:"asked,omitempty"` Asked bool `json:"asked,omitempty"`
// Tries is how many times it was asked again: each time under a bus message id of its own, also across a start.
Tries int `json:"tries,omitempty"`
} }
// forWantOfRoom says whether a refusal's words are the router's for an asker that already holds its most open asks // forWantOfRoom says whether a refusal's words are the router's for an asker that already holds its most open asks
@@ -591,15 +599,58 @@ func (t *Teller) endWait(id string) {
// OnCurrent is the watch of the gate's asks having handed over every key it holds now, at the teller's start: an ask that // OnCurrent is the watch of the gate's asks having handed over every key it holds now, at the teller's start: an ask that
// waited for room while the teller was away is asked again (the asks that held the room may have ended meanwhile). // waited for room while the teller was away is asked again (the asks that held the room may have ended meanwhile).
//
// An ask asked again whose word the teller did not hear (the router's warrants come on a plain subscription) waits once
// more when the router holds no record of it: the router keeps none of a refused ask (review of 38116fe0). Up to
// asks.MostOpen are asked, the most the router can have room for.
func (t *Teller) OnCurrent() { func (t *Teller) OnCurrent() {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
t.askWaiting() ws, rec, err := t.waiting()
if err != nil {
t.logf("%v", err)
return
}
changed := false
for i, x := range ws {
if !x.Asked {
continue
}
state, w, err := t.Asker.Where(x.ID)
if err != nil {
t.logf("the router's record of the ask %s could not be read: %v", x.ID, err)
continue
}
if state == "" && w == nil {
t.logf("%s: the ask %s, asked again, is not on the router's record: its refusal was not heard, and it waits again", x.Key, x.ID)
ws[i].Asked, changed = false, true
}
}
if changed && t.keepWaiting(ws, rec) != nil {
return
}
t.askWaiting(asks.MostOpen)
} }
// askWaiting asks again the first ask that waits for room and is not asked yet, dropping on the way every one the gate no // unheard puts an ask the teller holds open and the router holds no record of in the wait for room, once.
// longer asks. One at a time: one ask ended, so there is room for one. func (t *Teller) unheard(id string) {
func (t *Teller) askWaiting() { ws, rec, err := t.waiting()
if err != nil {
t.logf("%v", err)
return
}
if slices.ContainsFunc(ws, func(x waitingAsk) bool { return x.ID == id }) {
return
}
s, _, _ := t.seenOf(id)
if t.keepWaiting(append(ws, waitingAsk{Key: s.Key, ID: id}), rec) == nil {
t.logf("%s: the ask %s is open here and not on the router's record: it waits to be asked again", s.Key, id)
}
}
// askWaiting asks again the first n asks that wait for room and are not asked yet, dropping on the way every one the gate
// no longer asks. One at an end: one ask ended, so there is room for one.
func (t *Teller) askWaiting(n int) {
ws, rec, err := t.waiting() ws, rec, err := t.waiting()
if err != nil { if err != nil {
t.logf("%v", err) t.logf("%v", err)
@@ -626,13 +677,17 @@ func (t *Teller) askWaiting() {
ws, changed = slices.Delete(ws, i, i+1), true ws, changed = slices.Delete(ws, i, i+1), true
continue continue
} }
if err := t.askAgain(a, fmt.Sprintf("%s.again-%d", a.ID, x.Refusals)); err != nil { ws[i].Tries, changed = x.Tries+1, true
if err := t.askAgain(a, fmt.Sprintf("%s.again-%d", a.ID, ws[i].Tries)); err != nil {
t.logf("%s: the ask %s, waiting for room, could not be asked again, and waits for the next end: %v", x.Key, x.ID, err) t.logf("%s: the ask %s, waiting for room, could not be asked again, and waits for the next end: %v", x.Key, x.ID, err)
return return
} }
ws[i].Asked, changed = true, true ws[i].Asked = true
t.logf("%s: the ask %s is asked of the operator again, now that one of the teller's asks ended (refused %d times for want of room)", x.Key, x.ID, x.Refusals) t.logf("%s: the ask %s is asked of the operator again (refused %d times for want of room)", x.Key, x.ID, x.Refusals)
return if n--; n <= 0 {
return
}
i++
} }
} }
@@ -140,6 +140,8 @@ func newBed(t *testing.T) *bed {
_ = json.Unmarshal(raw, &a) _ = json.Unmarshal(raw, &a)
if b.seenIDs[a.ID] { if b.seenIDs[a.ID] {
b.dropped = append(b.dropped, a.ID) b.dropped = append(b.dropped, a.ID)
} else if _, kept := b.records[a.ID]; !kept {
b.records[a.ID] = `{"state":"open"}` // the router keeps the ask it took (asks.go CreateAsk)
} }
b.seenIDs[a.ID] = true b.seenIDs[a.ID] = true
} }