fix: Fixed the bug where the bus was closed and consumers had unfinished messages.

This commit is contained in:
tong3jie 2026-03-06 06:02:33 +00:00
parent 0c94e6f7b3
commit 24d06c0629

View file

@ -3,6 +3,7 @@ package bus
import ( import (
"context" "context"
"errors" "errors"
"sync"
"sync/atomic" "sync/atomic"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
@ -19,6 +20,7 @@ type MessageBus struct {
outboundMedia chan OutboundMediaMessage outboundMedia chan OutboundMediaMessage
done chan struct{} done chan struct{}
closed atomic.Bool closed atomic.Bool
wg sync.WaitGroup
} }
func NewMessageBus() *MessageBus { func NewMessageBus() *MessageBus {
@ -31,6 +33,9 @@ func NewMessageBus() *MessageBus {
} }
func (mb *MessageBus) PublishInbound(ctx context.Context, msg InboundMessage) error { func (mb *MessageBus) PublishInbound(ctx context.Context, msg InboundMessage) error {
mb.wg.Add(1)
defer mb.wg.Done()
if mb.closed.Load() { if mb.closed.Load() {
return ErrBusClosed return ErrBusClosed
} }
@ -59,6 +64,9 @@ func (mb *MessageBus) ConsumeInbound(ctx context.Context) (InboundMessage, bool)
} }
func (mb *MessageBus) PublishOutbound(ctx context.Context, msg OutboundMessage) error { func (mb *MessageBus) PublishOutbound(ctx context.Context, msg OutboundMessage) error {
mb.wg.Add(1)
defer mb.wg.Done()
if mb.closed.Load() { if mb.closed.Load() {
return ErrBusClosed return ErrBusClosed
} }
@ -87,6 +95,9 @@ func (mb *MessageBus) SubscribeOutbound(ctx context.Context) (OutboundMessage, b
} }
func (mb *MessageBus) PublishOutboundMedia(ctx context.Context, msg OutboundMediaMessage) error { func (mb *MessageBus) PublishOutboundMedia(ctx context.Context, msg OutboundMediaMessage) error {
mb.wg.Add(1)
defer mb.wg.Done()
if mb.closed.Load() { if mb.closed.Load() {
return ErrBusClosed return ErrBusClosed
} }
@ -115,43 +126,39 @@ func (mb *MessageBus) SubscribeOutboundMedia(ctx context.Context) (OutboundMedia
} }
func (mb *MessageBus) Close() { func (mb *MessageBus) Close() {
if mb.closed.CompareAndSwap(false, true) { if !mb.closed.CompareAndSwap(false, true) {
return
}
// Notify all Pub/Sub that i will be closing down
close(mb.done) close(mb.done)
// Drain buffered channels so messages aren't silently lost. // Make sure all Pub was done
// Channels are NOT closed to avoid send-on-closed panics from concurrent publishers. mb.wg.Wait()
// Batch empty the chan
drained := 0 drained := 0
for {
select { drained += drain(mb.inbound)
case <-mb.inbound: drained += drain(mb.outbound)
drained++ drained += drain(mb.outboundMedia)
default:
goto doneInbound
}
}
doneInbound:
for {
select {
case <-mb.outbound:
drained++
default:
goto doneOutbound
}
}
doneOutbound:
for {
select {
case <-mb.outboundMedia:
drained++
default:
goto doneMedia
}
}
doneMedia:
if drained > 0 { if drained > 0 {
logger.DebugCF("bus", "Drained buffered messages during close", map[string]any{ logger.DebugCF("bus", "Drained buffered messages during close", map[string]any{
"count": drained, "count": drained,
}) })
} }
}
func drain[T any](ch <-chan T) int {
n := 0
for {
select {
case <-ch:
n++
default:
return n
}
} }
} }