One guarded handler slot for a bundle's event and heartbeat handlers, so a reload never races the consumer's loop

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 16:28:08 +01:00
parent db3b5fe63d
commit 7390652f33
3 changed files with 27 additions and 17 deletions
+8 -1
View File
@@ -160,7 +160,7 @@ func TestALongRunningBundleThatExitsIsStartedAgain(t *testing.T) {
func fmtSprintf(format string, args ...any) string { return fmt.Sprintf(format, args...) }
func errorf(format string, args ...any) error { return fmt.Errorf(format, args...) }
// A bundle loaded again sets its heartbeat handler while the consumer's loop hands a word on: no data race (-race),
// A bundle loaded again sets its event and heartbeat handlers while the consumer's loop hands a word or an event on: no data race (-race),
// and the heartbeat is asked of the bus only while some bundle holds a handler.
func TestAHeartbeatHandlerSetAgainWhileWordsAreHandedOnIsNotARace(t *testing.T) {
m := &moduleEvents{}
@@ -170,16 +170,23 @@ func TestAHeartbeatHandlerSetAgainWhileWordsAreHandedOnIsNotARace(t *testing.T)
if m.beatAsked() {
t.Fatal("a heartbeat is asked while no bundle gave a handler")
}
took := &deliverSlot{}
took.set(func(json.RawMessage) error { return nil })
m.delivers = []*deliverSlot{took}
var got atomic.Int32
done := make(chan struct{})
go func() {
defer close(done)
for i := 0; i < 200; i++ {
slot.set(func(json.RawMessage) { got.Add(1) })
took.set(func(json.RawMessage) error { got.Add(1); return nil })
}
}()
for i := 0; i < 200; i++ {
all.handBeat(m, bus.Beat{At: time.Now()})
if err := all.handOut("mod", m, bus.Envelope{Key: "a.b", Body: json.RawMessage(`{}`)}); err != nil {
t.Fatal(err)
}
}
<-done
if !m.beatAsked() {