Files
queue/broker.go
what 1322280daf fix: 修复无订阅者时消息静默丢失问题,完善测试
- 新增 pending 缓冲区,publish 时若无订阅者则暂存消息
- subscribe 时自动将缓冲消息投入 channel,解决服务重启后恢复任务丢失的问题
- 去除 broadcast 5ms 超时导致的消息丢失
- chan bool 改为 chan struct{},RWMutex 改为 Mutex
- 新增 broker_test.go,12 个单元测试覆盖核心场景(含 -race)
- 为 client_test.go 中的无限循环 demo 添加 t.Skip()
2026-06-02 19:21:47 +08:00

121 lines
2.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package queue
import (
"errors"
"sync"
)
type Broker struct {
exit chan struct{}
capacity int
topics map[string][]chan any
pending map[string][]any // 订阅前发布的消息缓冲subscribe 时一次性投递
mu sync.Mutex
}
func (b *Broker) setConditions(capacity int) {
b.mu.Lock()
b.capacity = capacity
b.mu.Unlock()
}
func (b *Broker) close() {
select {
case <-b.exit:
return
default:
close(b.exit)
b.mu.Lock()
b.topics = make(map[string][]chan any)
b.pending = make(map[string][]any)
b.mu.Unlock()
}
}
// publish 推送消息;若暂无订阅者则缓冲,等待订阅者注册后投递。
func (b *Broker) publish(topic string, msg any) error {
select {
case <-b.exit:
return errors.New("broker closed")
default:
}
b.mu.Lock()
subs := b.topics[topic]
if len(subs) == 0 {
b.pending[topic] = append(b.pending[topic], msg)
b.mu.Unlock()
return nil
}
// 持有锁期间只做列表复制,发送在锁外进行,避免阻塞其他 publish
chs := make([]chan any, len(subs))
copy(chs, subs)
b.mu.Unlock()
for _, ch := range chs {
select {
case ch <- msg:
case <-b.exit:
return errors.New("broker closed")
}
}
return nil
}
// subscribe 订阅 topic返回 channel同时将该 topic 的缓冲消息立即投入 channel。
func (b *Broker) subscribe(topic string) (<-chan any, error) {
select {
case <-b.exit:
return nil, errors.New("broker closed")
default:
}
b.mu.Lock()
capacity := b.capacity
if capacity <= 0 {
capacity = 10
}
ch := make(chan any, capacity)
b.topics[topic] = append(b.topics[topic], ch)
buffered := b.pending[topic]
delete(b.pending, topic)
b.mu.Unlock()
// channel 刚创建必然不满,直接写入不会阻塞
for _, msg := range buffered {
ch <- msg
}
return ch, nil
}
func (b *Broker) unsubscribe(topic string, sub <-chan any) error {
select {
case <-b.exit:
return errors.New("broker closed")
default:
}
b.mu.Lock()
defer b.mu.Unlock()
subs := b.topics[topic]
newSubs := subs[:0]
for _, s := range subs {
if s != sub {
newSubs = append(newSubs, s)
}
}
b.topics[topic] = newSubs
return nil
}
func NewBroker() *Broker {
return &Broker{
exit: make(chan struct{}),
capacity: 10,
topics: make(map[string][]chan any),
pending: make(map[string][]any),
}
}