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
+17 -14
View File
@@ -774,29 +774,34 @@ type consumers struct {
type moduleEvents struct {
stop func()
delivers []*func(json.RawMessage) error
delivers []*deliverSlot
beats []*beatSlot
}
// beatSlot is where a bundle's heartbeat handler is kept; a bundle loaded again sets it while the consumer's loop
// reads it, so both go through its own lock.
type beatSlot struct {
// slot is where a bundle's handler is kept (its event handler, its heartbeat handler); a bundle loaded again sets it
// while the consumer's loop reads it, so both go through its own lock.
type slot[F any] struct {
mu sync.Mutex
f func(json.RawMessage)
f F
}
func (s *beatSlot) get() func(json.RawMessage) {
func (s *slot[F]) get() F {
s.mu.Lock()
defer s.mu.Unlock()
return s.f
}
func (s *beatSlot) set(f func(json.RawMessage)) {
func (s *slot[F]) set(f F) {
s.mu.Lock()
defer s.mu.Unlock()
s.f = f
}
type (
deliverSlot = slot[func(json.RawMessage) error]
beatSlot = slot[func(json.RawMessage)]
)
// beatAsked says whether any bundle of the module asked for the heartbeat; the caller holds c.mu.
func (m *moduleEvents) beatAsked() bool {
for _, t := range m.beats {
@@ -851,7 +856,7 @@ type moduleBus struct {
all *consumers
module string
mu sync.Mutex
deliver *func(json.RawMessage) error
deliver *deliverSlot
beat *beatSlot
}
@@ -893,11 +898,9 @@ func (b *moduleBus) Ask(params json.RawMessage) (json.RawMessage, error) {
func (b *moduleBus) Subscribe(deliver func(json.RawMessage) error, beat func(json.RawMessage)) error {
b.mu.Lock()
if b.deliver == nil {
d := deliver
b.deliver = &d
} else {
*b.deliver = deliver
b.deliver = &deliverSlot{}
}
b.deliver.set(deliver)
mine := b.deliver
if b.beat == nil {
b.beat = &beatSlot{}
@@ -976,10 +979,10 @@ func (c *consumers) handOut(module string, m *moduleEvents, env bus.Envelope) er
c.mu.Unlock()
return errModuleReloading
}
targets := append([]*func(json.RawMessage) error(nil), m.delivers...)
targets := append([]*deliverSlot(nil), m.delivers...)
c.mu.Unlock()
for _, d := range targets {
if err := (*d)(raw); err != nil {
if err := d.get()(raw); err != nil {
c.logf("[mesh-tools] %s did not take %s%s: %s; offered again in %s", module, env.Key,
eventRef(env), whyNotTaken(err), bus.NakDelay)
return err