seatlist: an event heard before the seat ever answered reads the whole list (issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -345,6 +345,16 @@ func (r *Reader) dropLocked(seat string) {
|
||||
// heard takes one of the seat's events: a trunk definition moved, so that seat is asked again, or went, so it is dropped.
|
||||
// An error is returned so the bus offers the event again.
|
||||
func (r *Reader) heard(key string, body json.RawMessage) error {
|
||||
r.mu.Lock()
|
||||
unread := !r.spoken
|
||||
r.mu.Unlock()
|
||||
if unread {
|
||||
// the seat was silent when the reader started and speaks now: read the whole list, not only the seat named
|
||||
if err := r.Reload(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var c Changed
|
||||
if err := json.Unmarshal(body, &c); err != nil || c.Seat == "" {
|
||||
return nil
|
||||
@@ -352,6 +362,14 @@ func (r *Reader) heard(key string, body json.RawMessage) error {
|
||||
if c.Source != "" && c.Source != SourceTrunk {
|
||||
return nil
|
||||
}
|
||||
r.mu.Lock()
|
||||
spoken := r.spoken
|
||||
r.mu.Unlock()
|
||||
if !spoken {
|
||||
// The seat was silent when this reader started: its event says it speaks now, so the whole list is read, not this
|
||||
// seat alone, and the reader is heard (MayAct) from here on.
|
||||
return r.Reload()
|
||||
}
|
||||
if strings.HasSuffix(key, "."+RemovedEvent) {
|
||||
r.mu.Lock()
|
||||
r.dropLocked(c.Seat)
|
||||
|
||||
@@ -218,3 +218,42 @@ func TestTheTwoToolsReadTheHeldListAndReadItAgainForTheirCallers(t *testing.T) {
|
||||
t.Fatalf("again: %v %+v", err, r.Status())
|
||||
}
|
||||
}
|
||||
|
||||
// A reader started while the seat was silent reads the whole list at the first event the seat sends, and then acts on any module.
|
||||
func TestAReaderStartedWhileTheSeatWasSilentReadsTheWholeListAtItsFirstEvent(t *testing.T) {
|
||||
owner := &fakeOwner{down: true, table: map[string]string{"node-b": def("node-b", "node", "put"), "node-c": def("node-c", "node", "put")}}
|
||||
r, err := Start(Config{Bus: owner, Bootstrap: Bootstrapped()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.Status().Heard || r.MayAct("node-b") {
|
||||
t.Fatal("the silent seat was heard")
|
||||
}
|
||||
owner.down = false
|
||||
if err := owner.say(ChangedEvent, "node-b", SourceTrunk); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !r.Status().Heard || !r.MayAct("node-b") || !r.Has("node-c") {
|
||||
t.Fatalf("the event did not read the whole list: %+v", r.Status())
|
||||
}
|
||||
}
|
||||
|
||||
// A reader started while the seat was silent is heard at the seat's changed event: the whole list is read then (ADR 0636 §1).
|
||||
func TestAReaderStartedWhileTheSeatWasSilentIsHeardAtItsChangedEvent(t *testing.T) {
|
||||
owner := &fakeOwner{down: true}
|
||||
r, err := Start(Config{Bus: owner, Bootstrap: Bootstrapped()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.Status().Heard || r.MayAct("node-files") {
|
||||
t.Fatal("the silent seat must leave the reader unheard")
|
||||
}
|
||||
owner.down = false
|
||||
owner.table = map[string]string{"node-a": def("node-a", "node", "put"), "node-b": def("node-b", "node", "put")}
|
||||
if err := owner.say(ChangedEvent, "node-a", SourceTrunk); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st := r.Status(); !st.Heard || st.LastError != "" || !r.MayAct("node-files") || !r.Has("node-a") || !r.Has("node-b") {
|
||||
t.Fatalf("after the changed event: %+v, node-b held %v: want heard and the whole list read", st, r.Has("node-b"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user