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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user